<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>学习笔记 | 学习笔记</title><meta name="author" content="lzoxun"><meta name="copyright" content="lzoxun"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="最近读《重学前端》，开篇就是让你拥有自己的知识体系图谱，后续学的东西补充到相应的模块，既可以加深对原有知识的理解，又可以强化记忆，很不错的学习方案。 这篇文章主要知识点来自：  《Node.js硬实战：115个核心技巧》 i0natan&#x2F;nodebestpractices 后续学习的一些知识点">
<meta property="og:type" content="article">
<meta property="og:title" content="学习笔记">
<meta property="og:url" content="http://example.com/2023/07/22/server/node/%E5%88%AB%E4%BA%BA%E7%9A%84%E6%96%87%E6%A1%A3/index.html">
<meta property="og:site_name" content="学习笔记">
<meta property="og:description" content="最近读《重学前端》，开篇就是让你拥有自己的知识体系图谱，后续学的东西补充到相应的模块，既可以加深对原有知识的理解，又可以强化记忆，很不错的学习方案。 这篇文章主要知识点来自：  《Node.js硬实战：115个核心技巧》 i0natan&#x2F;nodebestpractices 后续学习的一些知识点">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png">
<meta property="article:published_time" content="2023-07-22T07:14:09.688Z">
<meta property="article:modified_time" content="2023-07-22T07:14:09.688Z">
<meta property="article:author" content="lzoxun">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="http://example.com/2023/07/22/server/node/%E5%88%AB%E4%BA%BA%E7%9A%84%E6%96%87%E6%A1%A3/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = {
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":true,"top_n_per_article":1,"unescape":true,"languages":{"hits_empty":"找不到您查询的内容：${query}","hits_stats":"共找到 ${hits} 篇文章"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  },
  autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '学习笔记',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-07-22 15:14:09'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
    win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      if (id) link.id = id
      link.onerror = reject
      link.onload = link.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        link.onload = link.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(link)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          const now = new Date()
          const hour = now.getHours()
          const isNight = hour <= 6 || hour >= 18
          if (t === undefined) isNight ? activateDarkMode() : activateLightMode()
          else if (t === 'light') activateLightMode()
          else activateDarkMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 6.3.0"></head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><div class="spinner-box"><div class="configure-border-1"><div class="configure-core"></div></div><div class="configure-border-2"><div class="configure-core"></div></div><div class="loading-word">加载中...</div></div></div><script>(()=>{
  const $loadingBox = document.getElementById('loading-box')
  const $body = document.body
  const preloader = {
    endLoading: () => {
      $body.style.overflow = ''
      $loadingBox.classList.add('loaded')
    },
    initLoading: () => {
      $body.style.overflow = 'hidden'
      $loadingBox.classList.remove('loaded')
    }
  }

  preloader.initLoading()
  window.addEventListener('load',() => { preloader.endLoading() })

  if (false) {
    document.addEventListener('pjax:send', () => { preloader.initLoading() })
    document.addEventListener('pjax:complete', () => { preloader.endLoading() })
  }
})()</script><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "" data-lazy-src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">108</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">1</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div><hr class="custom-hr"/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><span id="blog-info"><a href="/" title="学习笔记"><span class="site-name">学习笔记</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">无题</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2023-07-22T07:14:09.688Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-07-22T07:14:09.688Z" title="更新于 2023-07-22 15:14:09">2023-07-22</time></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title=""><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><!--NodeJS-cheat-sheet-->

<p>最近读《重学前端》，开篇就是让你拥有自己的知识体系图谱，后续学的东西补充到相应的模块，既可以加深对原有知识的理解，又可以强化记忆，很不错的学习方案。</p>
<p>这篇文章主要知识点来自：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://www.amazon.cn/dp/B01MYX8XG1">《Node.js硬实战：115个核心技巧》</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/i0natan/nodebestpractices">i0natan&#x2F;nodebestpractices</a></li>
<li>后续学习的一些知识点</li>
</ul>
<span id="more"></span>

<h1 id="docsify-文档"><a href="#docsify-文档" class="headerlink" title="docsify 文档"></a>docsify 文档</h1><p><a target="_blank" rel="noopener" href="https://static.chenng.cn/#/%E5%9F%BA%E7%A1%80-%E5%90%8E%E7%AB%AF/NodeJS">https://static.chenng.cn/#/%E5%9F%BA%E7%A1%80-%E5%90%8E%E7%AB%AF/NodeJS</a></p>
<h1 id="更新记录"><a href="#更新记录" class="headerlink" title="更新记录"></a>更新记录</h1><p><a target="_blank" rel="noopener" href="https://github.com/ringcrl/node-point/commits/master">CHANGE LOG</a></p>
<h1 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h1><p>比较好的 markdown 的查看方式是直接用 VSCode 打开大纲，这样整个脉络一目了然，后续补充知识点也很快定位到相应的位置：</p>
<p><img src= "" data-lazy-src="https://qiniu.chenng.cn/2019-01-28-09-44-31.png" alt="01.png"></p>
<p>这个 markdown 文件已经丢到 Github，有更新会直接推这里：</p>
<p><a target="_blank" rel="noopener" href="https://github.com/ringcrl/node-point">https://github.com/ringcrl/node-point</a></p>
<p>博客上也会记录一些好玩的东西：</p>
<p><a target="_blank" rel="noopener" href="http://www.chenng.cn/archives/">www.chenng.cn/archives/</a></p>
<h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用 nvm 安装</span></span><br><span class="line">https://github.com/creationix/nvm<span class="comment">#install-script # Git install</span></span><br><span class="line"></span><br><span class="line">nvm install</span><br><span class="line">nvm <span class="built_in">alias</span> default</span><br><span class="line"></span><br><span class="line"><span class="comment"># 卸载 pkg 安装版</span></span><br><span class="line">sudo <span class="built_in">rm</span> -rf /usr/local/&#123;bin/&#123;node,npm&#125;,lib/node_modules/npm,lib/node,share/man/*/node.*&#125;</span><br></pre></td></tr></table></figure>

<h1 id="全局变量"><a href="#全局变量" class="headerlink" title="全局变量"></a>全局变量</h1><h2 id="require-id"><a href="#require-id" class="headerlink" title="require(id)"></a>require(id)</h2><ul>
<li>内建模块直接从内存加载</li>
<li>文件模块通过文件查找定位到文件</li>
<li>包通过 package.json 里面的 main 字段查找入口文件</li>
</ul>
<h3 id="module-exports"><a href="#module-exports" class="headerlink" title="module.exports"></a>module.exports</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过如下模块包装得到</span></span><br><span class="line">(funciton (<span class="built_in">exports</span>, <span class="built_in">require</span>, <span class="variable language_">module</span>, __filename, __dirname) &#123; <span class="comment">// 包装头</span></span><br><span class="line"></span><br><span class="line">&#125;); <span class="comment">// 包装尾</span></span><br></pre></td></tr></table></figure>

<h3 id="JSON-文件"><a href="#JSON-文件" class="headerlink" title="JSON 文件"></a>JSON 文件</h3><ul>
<li>通过 <code>fs.readFileSync()</code> 加载</li>
<li>通过 <code>JSON.parse()</code> 解析</li>
</ul>
<h3 id="加载大文件"><a href="#加载大文件" class="headerlink" title="加载大文件"></a>加载大文件</h3><ul>
<li>require 成功后会缓存文件</li>
<li>大量使用会导致大量数据驻留在内存中，导致 GC 频分和内存泄露</li>
</ul>
<h2 id="module-exports-和-exports"><a href="#module-exports-和-exports" class="headerlink" title="module.exports 和 exports"></a>module.exports 和 exports</h2><h3 id="执行时"><a href="#执行时" class="headerlink" title="执行时"></a>执行时</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(<span class="title function_">funciton</span>(<span class="params"><span class="built_in">exports</span>, <span class="built_in">require</span>, <span class="variable language_">module</span>, __filename, __dirname</span>) &#123; <span class="comment">// 包装头</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello world!&#x27;</span>) <span class="comment">// 原始文件</span></span><br><span class="line">&#125;); <span class="comment">// 包装尾</span></span><br></pre></td></tr></table></figure>

<h3 id="exports"><a href="#exports" class="headerlink" title="exports"></a>exports</h3><ul>
<li>exports 是 module 的属性，默认情况是空对象</li>
<li>require 一个模块实际得到的是该模块的 exports 属性</li>
<li>exports.xxx 导出具有多个属性的对象</li>
<li>module.exports &#x3D; xxx 导出一个对象</li>
</ul>
<h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// module-2.js</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">method</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;Hello&#x27;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">method2</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;Hello again&#x27;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// module-1.js</span></span><br><span class="line"><span class="keyword">const</span> module2 = <span class="built_in">require</span>(<span class="string">&#x27;./module-2&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(module2.<span class="title function_">method</span>()); <span class="comment">// Hello</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(module2.<span class="title function_">method2</span>()); <span class="comment">// Hello again</span></span><br></pre></td></tr></table></figure>

<h2 id="路径变量"><a href="#路径变量" class="headerlink" title="路径变量"></a>路径变量</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;__dirname:&#x27;</span>, __dirname); <span class="comment">// 文件夹</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;__filename:&#x27;</span>, __filename); <span class="comment">// 文件</span></span><br><span class="line"></span><br><span class="line">path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;views&#x27;</span>, <span class="string">&#x27;view.html&#x27;</span>); <span class="comment">// 如果不希望自己手动处理 / 的问题，使用 path.join</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// HOME 目录</span></span><br><span class="line"><span class="keyword">const</span> homeDir =  <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>).<span class="title function_">homedir</span>();</span><br></pre></td></tr></table></figure>

<h2 id="console"><a href="#console" class="headerlink" title="console"></a>console</h2><table>
<thead>
<tr>
<th align="center">占位符</th>
<th align="center">类型</th>
<th align="center">例子</th>
</tr>
</thead>
<tbody><tr>
<td align="center">%s</td>
<td align="center">String</td>
<td align="center">console.log(‘%s’, ‘value’)</td>
</tr>
<tr>
<td align="center">%d</td>
<td align="center">Number</td>
<td align="center">console.log(‘%d’, 3.14)</td>
</tr>
<tr>
<td align="center">%j</td>
<td align="center">JSON</td>
<td align="center">console.log(‘%j’, {name: ‘Chenng’})</td>
</tr>
</tbody></table>
<h2 id="process"><a href="#process" class="headerlink" title="process"></a>process</h2><h3 id="查看-PATH"><a href="#查看-PATH" class="headerlink" title="查看 PATH"></a>查看 PATH</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">node</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(process.<span class="property">env</span>.<span class="property">PATH</span>.<span class="title function_">split</span>(<span class="string">&#x27;:&#x27;</span>).<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>));</span><br></pre></td></tr></table></figure>

<h3 id="设置-PATH"><a href="#设置-PATH" class="headerlink" title="设置 PATH"></a>设置 PATH</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="property">env</span>.<span class="property">PATH</span> += <span class="string">&#x27;:/a_new_path_to_executables&#x27;</span>;</span><br></pre></td></tr></table></figure>

<h3 id="获取信息"><a href="#获取信息" class="headerlink" title="获取信息"></a>获取信息</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取平台信息</span></span><br><span class="line">process.<span class="property">arch</span> <span class="comment">// x64</span></span><br><span class="line">process.<span class="property">platform</span> <span class="comment">// darwin</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取内存使用情况</span></span><br><span class="line">process.<span class="title function_">memoryUsage</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取命令行参数</span></span><br><span class="line">process.<span class="property">argv</span></span><br></pre></td></tr></table></figure>

<h3 id="nextTick"><a href="#nextTick" class="headerlink" title="nextTick"></a>nextTick</h3><p>process.nextTick 方法允许你把一个回调放在下一次时间轮询队列的头上，这意味着可以用来延迟执行，结果是比 setTimeout 更有效率。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">EventEmitter</span> = <span class="built_in">require</span>(<span class="string">&#x27;events&#x27;</span>).<span class="property">EventEmitter</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">complexOperations</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> events = <span class="keyword">new</span> <span class="title class_">EventEmitter</span>();</span><br><span class="line"></span><br><span class="line">  process.<span class="title function_">nextTick</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    events.<span class="title function_">emit</span>(<span class="string">&#x27;success&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> events;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">complexOperations</span>().<span class="title function_">on</span>(<span class="string">&#x27;success&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;success!&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h1 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h1><p>如果没有提供编码格式，文件操作以及很多网络操作就会将数据作为 Buffer 类型返回。</p>
<h2 id="toString-NaN"><a href="#toString-NaN" class="headerlink" title="toString"></a>toString</h2><p>默认转为 <code>UTF-8</code> 格式，还支持 <code>ascii</code>、<code>base64</code> 等。</p>
<h2 id="data-URI"><a href="#data-URI" class="headerlink" title="data URI"></a>data URI</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 生成 data URI</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> mime = <span class="string">&#x27;image/png&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> encoding = <span class="string">&#x27;base64&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> base64Data = fs.<span class="title function_">readFileSync</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/monkey.png`</span>).<span class="title function_">toString</span>(encoding);</span><br><span class="line"><span class="keyword">const</span> uri = <span class="string">`data:<span class="subst">$&#123;mime&#125;</span>;<span class="subst">$&#123;encoding&#125;</span>,<span class="subst">$&#123;base64Data&#125;</span>`</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(uri);</span><br><span class="line"></span><br><span class="line"><span class="comment">// data URI 转文件</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> uri = <span class="string">&#x27;...&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> base64Data = uri.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>)[<span class="number">1</span>];</span><br><span class="line"><span class="keyword">const</span> buf = <span class="title class_">Buffer</span>(base64Data, <span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line">fs.<span class="title function_">writeFileSync</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/secondmonkey.png`</span>, buf);</span><br></pre></td></tr></table></figure>

<h1 id="events"><a href="#events" class="headerlink" title="events"></a>events</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">EventEmitter</span> = <span class="built_in">require</span>(<span class="string">&#x27;events&#x27;</span>).<span class="property">EventEmitter</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AudioDevice</span> = &#123;</span><br><span class="line">  <span class="attr">play</span>: <span class="keyword">function</span> (<span class="params">track</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;play&#x27;</span>, track);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">stop</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stop&#x27;</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MusicPlayer</span> <span class="keyword">extends</span> <span class="title class_ inherited__">EventEmitter</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">playing</span> = <span class="literal">false</span>; </span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> musicPlayer = <span class="keyword">new</span> <span class="title class_">MusicPlayer</span>();</span><br><span class="line">musicPlayer.<span class="title function_">on</span>(<span class="string">&#x27;play&#x27;</span>, <span class="keyword">function</span> (<span class="params">track</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">playing</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="title class_">AudioDevice</span>.<span class="title function_">play</span>(track);</span><br><span class="line">&#125;);</span><br><span class="line">musicPlayer.<span class="title function_">on</span>(<span class="string">&#x27;stop&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">playing</span> = <span class="literal">false</span>;</span><br><span class="line">  <span class="title class_">AudioDevice</span>.<span class="title function_">stop</span>();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">musicPlayer.<span class="title function_">emit</span>(<span class="string">&#x27;play&#x27;</span>, <span class="string">&#x27;The Roots - The Fire&#x27;</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  musicPlayer.<span class="title function_">emit</span>(<span class="string">&#x27;stop&#x27;</span>);</span><br><span class="line">&#125;, <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理异常</span></span><br><span class="line"><span class="comment">// EventEmitter 实例发生错误会发出一个 error 事件</span></span><br><span class="line"><span class="comment">// 如果没有监听器，默认动作是打印一个堆栈并退出程序</span></span><br><span class="line">musicPlayer.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">err</span>(<span class="string">&#x27;Error:&#x27;</span>, err);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h1 id="util"><a href="#util" class="headerlink" title="util"></a>util</h1><h2 id="promisify"><a href="#promisify" class="headerlink" title="promisify"></a>promisify</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> readAsync = util.<span class="title function_">promisify</span>(fs.<span class="property">readFile</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> data = <span class="keyword">await</span> <span class="title function_">readAsync</span>(<span class="string">&#x27;./package.json&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    data  =<span class="title class_">JSON</span>.<span class="title function_">parse</span>(data);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data.<span class="property">name</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="流"><a href="#流" class="headerlink" title="流"></a>流</h1><h2 id="理解流"><a href="#理解流" class="headerlink" title="理解流"></a>理解流</h2><p>流是基于事件的 API，用于管理和处理数据。</p>
<ul>
<li>流是能够读写的</li>
<li>是基于事件实现的一个实例</li>
</ul>
<p>理解流的最好方式就是想象一下没有流的时候怎么处理数据：</p>
<ul>
<li><code>fs.readFileSync</code> 同步读取文件，程序会阻塞，所有数据被读到内存</li>
<li><code>fs.readFile</code> 阻止程序阻塞，但仍会将文件所有数据读取到内存中</li>
<li>希望少内存读取大文件，读取一个数据块到内存处理完再去索取更多的数据</li>
</ul>
<h3 id="流的类型"><a href="#流的类型" class="headerlink" title="流的类型"></a>流的类型</h3><ul>
<li>内置：许多核心模块都实现了流接口，如 <code>fs.createReadStream</code></li>
<li>HTTP：处理网络技术的流</li>
<li>解释器：第三方模块 XML、JSON 解释器</li>
<li>浏览器：Node 流可以被拓展使用在浏览器</li>
<li>Audio：流接口的声音模块</li>
<li>RPC（远程调用）：通过网络发送流是进程间通信的有效方式</li>
<li>测试：使用流的测试库</li>
</ul>
<h2 id="使用内建流-API"><a href="#使用内建流-API" class="headerlink" title="使用内建流 API"></a>使用内建流 API</h2><h3 id="静态-web-服务器"><a href="#静态-web-服务器" class="headerlink" title="静态 web 服务器"></a>静态 web 服务器</h3><p>想要通过网络高效且支持大文件的发送一个文件到一个客户端。</p>
<h4 id="不使用流"><a href="#不使用流" class="headerlink" title="不使用流"></a>不使用流</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  fs.<span class="title function_">readFile</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/index.html`</span>, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      res.<span class="property">statusCode</span> = <span class="number">500</span>;</span><br><span class="line">      res.<span class="title function_">end</span>(<span class="title class_">String</span>(err));</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    res.<span class="title function_">end</span>(data);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8000</span>);</span><br></pre></td></tr></table></figure>

<h4 id="使用流"><a href="#使用流" class="headerlink" title="使用流"></a>使用流</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  fs.<span class="title function_">createReadStream</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/index.html`</span>).<span class="title function_">pipe</span>(res);</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8000</span>);</span><br></pre></td></tr></table></figure>

<ul>
<li>更少代码，更加高效</li>
<li>提供一个缓冲区发送到客户端</li>
</ul>
<h4 id="使用流-gzip"><a href="#使用流-gzip" class="headerlink" title="使用流 + gzip"></a>使用流 + gzip</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">&#x27;zlib&#x27;</span>);</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123;</span><br><span class="line">    <span class="string">&#x27;content-encoding&#x27;</span>: <span class="string">&#x27;gzip&#x27;</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">  fs.<span class="title function_">createReadStream</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/index.html`</span>)</span><br><span class="line">    .<span class="title function_">pipe</span>(zlib.<span class="title function_">createGzip</span>())</span><br><span class="line">    .<span class="title function_">pipe</span>(res);</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8000</span>);</span><br></pre></td></tr></table></figure>

<h3 id="流的错误处理"><a href="#流的错误处理" class="headerlink" title="流的错误处理"></a>流的错误处理</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> stream = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;not-found&#x27;</span>);</span><br><span class="line"></span><br><span class="line">stream.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">trace</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;Stack:&#x27;</span>, err.<span class="property">stack</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;The error raised was:&#x27;</span>, err);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="使用流基类"><a href="#使用流基类" class="headerlink" title="使用流基类"></a>使用流基类</h2><h3 id="可读流-JSON-行解析器"><a href="#可读流-JSON-行解析器" class="headerlink" title="可读流 - JSON 行解析器"></a>可读流 - JSON 行解析器</h3><p>可读流被用来为 I&#x2F;O 源提供灵活的 API，也可以被用作解析器：</p>
<ul>
<li>继承自 steam.Readable 类</li>
<li>并实现一个 <code>_read(size)</code> 方法</li>
</ul>
<p>json-lines.txt</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123; &quot;position&quot;: 0, &quot;letter&quot;: &quot;a&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 1, &quot;letter&quot;: &quot;b&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 2, &quot;letter&quot;: &quot;c&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 3, &quot;letter&quot;: &quot;d&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 4, &quot;letter&quot;: &quot;e&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 5, &quot;letter&quot;: &quot;f&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 6, &quot;letter&quot;: &quot;g&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 7, &quot;letter&quot;: &quot;h&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 8, &quot;letter&quot;: &quot;i&quot; &#125;</span><br><span class="line">&#123; &quot;position&quot;: 9, &quot;letter&quot;: &quot;j&quot; &#125;</span><br></pre></td></tr></table></figure>

<p>JSONLineReader.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> stream = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">JSONLineReader</span> <span class="keyword">extends</span> <span class="title class_ inherited__">stream.Readable</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">source</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">_source</span> = source;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">_foundLineEnd</span> = <span class="literal">false</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">_buffer</span> = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    source.<span class="title function_">on</span>(<span class="string">&#x27;readable&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">read</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 所有定制 stream.Readable 类都需要实现 _read 方法</span></span><br><span class="line">  <span class="title function_">_read</span>(<span class="params">size</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> chunk;</span><br><span class="line">    <span class="keyword">let</span> line;</span><br><span class="line">    <span class="keyword">let</span> result;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">_buffer</span>.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">      chunk = <span class="variable language_">this</span>.<span class="property">_source</span>.<span class="title function_">read</span>();</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">_buffer</span> += chunk;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> lineIndex = <span class="variable language_">this</span>.<span class="property">_buffer</span>.<span class="title function_">indexOf</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (lineIndex !== -<span class="number">1</span>) &#123;</span><br><span class="line">      line = <span class="variable language_">this</span>.<span class="property">_buffer</span>.<span class="title function_">slice</span>(<span class="number">0</span>, lineIndex); <span class="comment">// 从 buffer 的开始截取第一行来获取一些文本进行解析</span></span><br><span class="line">      <span class="keyword">if</span> (line) &#123;</span><br><span class="line">        result = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(line);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">_buffer</span> = <span class="variable language_">this</span>.<span class="property">_buffer</span>.<span class="title function_">slice</span>(lineIndex + <span class="number">1</span>);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">emit</span>(<span class="string">&#x27;object&#x27;</span>, result); <span class="comment">// 当一个 JSON 记录解析出来的时候，触发一个 object 事件</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">push</span>(util.<span class="title function_">inspect</span>(result)); <span class="comment">// 将解析好的 SJON 发回内部队列</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">_buffer</span> = <span class="variable language_">this</span>.<span class="property">_buffer</span>.<span class="title function_">slice</span>(<span class="number">1</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> input = fs.<span class="title function_">createReadStream</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/json-lines.txt`</span>, &#123;</span><br><span class="line">  <span class="attr">encoding</span>: <span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">const</span> jsonLineReader = <span class="keyword">new</span> <span class="title class_">JSON</span>LineReader(input); <span class="comment">// 创建一个 JSONLineReader 实例，传递一个文件流给它处理</span></span><br><span class="line"></span><br><span class="line">jsonLineReader.<span class="title function_">on</span>(<span class="string">&#x27;object&#x27;</span>, <span class="function">(<span class="params">obj</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;pos:&#x27;</span>, obj.<span class="property">position</span>, <span class="string">&#x27;- letter:&#x27;</span>, obj.<span class="property">letter</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="可写流-文字变色"><a href="#可写流-文字变色" class="headerlink" title="可写流 - 文字变色"></a>可写流 - 文字变色</h3><p>可写的流可用于输出数据到底层 I&#x2F;O:</p>
<ul>
<li>继承自 <code>stream.Writable</code></li>
<li>实现一个 <code>_write</code> 方法向底层源数据发送数据</li>
</ul>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> json-lines.txt | node stram_writable.js</span><br></pre></td></tr></table></figure>

<p>stram_writable.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> stream = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GreenStream</span> <span class="keyword">extends</span> <span class="title class_ inherited__">stream.Writable</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(options);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">_write</span>(<span class="params">chunk, encoding, cb</span>) &#123;</span><br><span class="line">    process.<span class="property">stdout</span>.<span class="title function_">write</span>(<span class="string">`\u001b[32m<span class="subst">$&#123;chunk&#125;</span>\u001b[39m`</span>);</span><br><span class="line">    <span class="title function_">cb</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">process.<span class="property">stdin</span>.<span class="title function_">pipe</span>(<span class="keyword">new</span> <span class="title class_">GreenStream</span>());</span><br></pre></td></tr></table></figure>

<h3 id="双工流-接受和转换数据"><a href="#双工流-接受和转换数据" class="headerlink" title="双工流 - 接受和转换数据"></a>双工流 - 接受和转换数据</h3><p>双工流允许发送和接受数据：</p>
<ul>
<li>继承自 <code>stream.Duplex</code></li>
<li>实现 <code>_read</code> 和 <code>_write</code> 方法</li>
</ul>
<h3 id="转换流-解析数据"><a href="#转换流-解析数据" class="headerlink" title="转换流 - 解析数据"></a>转换流 - 解析数据</h3><p>使用流改变数据为另一种格式，并且高效地管理内存：</p>
<ul>
<li>继承自 <code>stream.Transform</code></li>
<li>实现 <code>_transform</code> 方法</li>
</ul>
<h2 id="测试流"><a href="#测试流" class="headerlink" title="测试流"></a>测试流</h2><p>使用 Node 内置的断言模块测试</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CSVParser</span> = <span class="built_in">require</span>(<span class="string">&#x27;./csvparser&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> parser = <span class="keyword">new</span> <span class="title class_">CSVParser</span>();</span><br><span class="line"><span class="keyword">const</span> actual = [];</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">createReadStream</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/sample.csv`</span>)</span><br><span class="line">  .<span class="title function_">pipe</span>(parser);</span><br><span class="line"></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  actual.<span class="title function_">push</span>(parser.<span class="title function_">read</span>());</span><br><span class="line">  actual.<span class="title function_">push</span>(parser.<span class="title function_">read</span>());</span><br><span class="line">  actual.<span class="title function_">push</span>(parser.<span class="title function_">read</span>());</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> expected = [</span><br><span class="line">    &#123; <span class="attr">name</span>: <span class="string">&#x27;Alex&#x27;</span>, <span class="attr">location</span>: <span class="string">&#x27;UK&#x27;</span>, <span class="attr">role</span>: <span class="string">&#x27;admin&#x27;</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">name</span>: <span class="string">&#x27;Sam&#x27;</span>, <span class="attr">location</span>: <span class="string">&#x27;France&#x27;</span>, <span class="attr">role</span>: <span class="string">&#x27;user&#x27;</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">name</span>: <span class="string">&#x27;John&#x27;</span>, <span class="attr">location</span>: <span class="string">&#x27;Canada&#x27;</span>, <span class="attr">role</span>: <span class="string">&#x27;user&#x27;</span> &#125;,</span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  assert.<span class="title function_">deepEqual</span>(expected, actual);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h1 id="文件系统"><a href="#文件系统" class="headerlink" title="文件系统"></a>文件系统</h1><h2 id="fs-模块交互"><a href="#fs-模块交互" class="headerlink" title="fs 模块交互"></a>fs 模块交互</h2><ul>
<li>POSIX 文件 I&#x2F;O</li>
<li>文件流</li>
<li>批量文件 I&#x2F;O</li>
<li>文件监控</li>
</ul>
<h2 id="POSIX-文件系统"><a href="#POSIX-文件系统" class="headerlink" title="POSIX 文件系统"></a>POSIX 文件系统</h2><table>
<thead>
<tr>
<th align="left">fs 方法</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">fs.truncate</td>
<td align="left">截断或者拓展文件到制定的长度</td>
</tr>
<tr>
<td align="left">fs.ftruncate</td>
<td align="left">和 truncate 一样，但将文件描述符作为参数</td>
</tr>
<tr>
<td align="left">fs.chown</td>
<td align="left">改变文件的所有者以及组</td>
</tr>
<tr>
<td align="left">fs.fchown</td>
<td align="left">和 chown 一样，但将文件描述符作为参数</td>
</tr>
<tr>
<td align="left">fs.lchown</td>
<td align="left">和 chown 一样，但不解析符号链接</td>
</tr>
<tr>
<td align="left">fs.stat</td>
<td align="left">获取文件状态</td>
</tr>
<tr>
<td align="left">fs.lstat</td>
<td align="left">和 stat 一样，但是返回信息是关于符号链接而不是它指向的内容</td>
</tr>
<tr>
<td align="left">fs.fstat</td>
<td align="left">和 stat 一样，但将文件描述符作为参数</td>
</tr>
<tr>
<td align="left">fs.link</td>
<td align="left">创建一个硬链接</td>
</tr>
<tr>
<td align="left">fs.symlink</td>
<td align="left">创建一个软连接</td>
</tr>
<tr>
<td align="left">fs.readlink</td>
<td align="left">读取一个软连接的值</td>
</tr>
<tr>
<td align="left">fs.realpath</td>
<td align="left">返回规范的绝对路径名</td>
</tr>
<tr>
<td align="left">fs.unlink</td>
<td align="left">删除文件</td>
</tr>
<tr>
<td align="left">fs.rmdir</td>
<td align="left">删除文件目录</td>
</tr>
<tr>
<td align="left">fs.mkdir</td>
<td align="left">创建文件目录</td>
</tr>
<tr>
<td align="left">fs.readdir</td>
<td align="left">读取一个文件目录的内容</td>
</tr>
<tr>
<td align="left">fs.close</td>
<td align="left">关闭一个文件描述符</td>
</tr>
<tr>
<td align="left">fs.open</td>
<td align="left">打开或者创建一个文件用来读取或者写入</td>
</tr>
<tr>
<td align="left">fs.utimes</td>
<td align="left">设置文件的读取和修改时间</td>
</tr>
<tr>
<td align="left">fs.futimes</td>
<td align="left">和 utimes 一样，但将文件描述符作为参数</td>
</tr>
<tr>
<td align="left">fs.fsync</td>
<td align="left">同步磁盘中的文件数据</td>
</tr>
<tr>
<td align="left">fs.write</td>
<td align="left">写入数据到一个文件</td>
</tr>
<tr>
<td align="left">fs.read</td>
<td align="left">读取一个文件的数据</td>
</tr>
</tbody></table>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fd = fs.<span class="title function_">openSync</span>(<span class="string">&#x27;./file.txt&#x27;</span>, <span class="string">&#x27;w+&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> writeBuf = <span class="keyword">new</span> <span class="title class_">Buffer</span>(<span class="string">&#x27;some data to write&#x27;</span>);</span><br><span class="line">fs.<span class="title function_">writeSync</span>(fd, writeBuf, <span class="number">0</span>, writeBuf.<span class="property">length</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> readBuf = <span class="keyword">new</span> <span class="title class_">Buffer</span>(writeBuf.<span class="property">length</span>);</span><br><span class="line">fs.<span class="title function_">readSync</span>(fd, readBuf, <span class="number">0</span>, writeBuf.<span class="property">length</span>, <span class="number">0</span>);</span><br><span class="line">assert.<span class="title function_">equal</span>(writeBuf.<span class="title function_">toString</span>(), readBuf.<span class="title function_">toString</span>());</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">closeSync</span>(fd);</span><br></pre></td></tr></table></figure>

<h2 id="读写流"><a href="#读写流" class="headerlink" title="读写流"></a>读写流</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> readable = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;./original.txt&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> writeable = fs.<span class="title function_">createWriteStream</span>(<span class="string">&#x27;./copy.txt&#x27;</span>);</span><br><span class="line">readable.<span class="title function_">pipe</span>(writeable);</span><br></pre></td></tr></table></figure>

<h2 id="文件监控"><a href="#文件监控" class="headerlink" title="文件监控"></a>文件监控</h2><p><code>fs.watchFile</code> 比 <code>fs.watch</code> 低效，但更好用。</p>
<h2 id="同步读取与-require"><a href="#同步读取与-require" class="headerlink" title="同步读取与 require"></a>同步读取与 require</h2><p>同步 fs 的方法应该在第一次初始化应用的时候使用。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> config = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(fs.<span class="title function_">readFileSync</span>(<span class="string">&#x27;./config.json&#x27;</span>).<span class="title function_">toString</span>());</span><br><span class="line"><span class="title function_">init</span>(config);</span><br></pre></td></tr></table></figure>

<p>require：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">&#x27;./config.json);</span></span><br><span class="line"><span class="string">init(config);</span></span><br></pre></td></tr></table></figure>

<ul>
<li>模块会被全局缓冲，其他文件也加载并修改，会影响到整个系统加载了此文件的模块</li>
<li>可以通过 <code>Object.freeze</code> 来冻结一个对象</li>
</ul>
<h2 id="文件描述"><a href="#文件描述" class="headerlink" title="文件描述"></a>文件描述</h2><p>文件描述是在操作系统中管理的在进程中打开文件所关联的一些数字或者索引。操作系统通过指派一个唯一的整数给每个打开的文件用来查看关于这个文件</p>
<table>
<thead>
<tr>
<th>Stream</th>
<th>文件描述</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>stdin</td>
<td>0</td>
<td>标准输入</td>
</tr>
<tr>
<td>stdout</td>
<td>1</td>
<td>标准输出</td>
</tr>
<tr>
<td>stderr</td>
<td>2</td>
<td>标准错误</td>
</tr>
</tbody></table>
<p><code>console.log(&#39;Log&#39;)</code> 是 <code>process.stdout.write(&#39;log&#39;)</code> 的语法糖。</p>
<p>一个文件描述是 open 以及 openSync 方法调用返回的一个数字</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fd = fs.<span class="title function_">openSync</span>(<span class="string">&#x27;myfile&#x27;</span>, <span class="string">&#x27;a&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> fd === <span class="string">&#x27;number&#x27;</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>

<h2 id="文件锁"><a href="#文件锁" class="headerlink" title="文件锁"></a>文件锁</h2><p>协同多个进程同时访问一个文件，保证文件的完整性以及数据不能丢失：</p>
<ul>
<li>强制锁（在内核级别执行）</li>
<li>咨询锁（非强制，只在涉及到进程订阅了相同的锁机制）<ul>
<li><code>node-fs-ext</code> 通过 <code>flock</code> 锁住一个文件</li>
</ul>
</li>
<li>使用锁文件<ul>
<li>进程 A 尝试创建一个锁文件，并且成功了</li>
<li>进程 A 已经获得了这个锁，可以修改共享的资源</li>
<li>进程 B 尝试创建一个锁文件，但失败了，无法修改共享的资源</li>
</ul>
</li>
</ul>
<p>Node 实现锁文件</p>
<ul>
<li>使用独占标记创建锁文件</li>
<li>使用 mkdir 创建锁文件</li>
</ul>
<h3 id="独占标记"><a href="#独占标记" class="headerlink" title="独占标记"></a>独占标记</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 所有需要打开文件的方法，fs.writeFile、fs.createWriteStream、fs.open 都有一个 x 标记</span></span><br><span class="line"><span class="comment">// 这个文件应该已独占打开，若这个文件存在，文件不能被打开</span></span><br><span class="line">fs.<span class="title function_">open</span>(<span class="string">&#x27;config.lock&#x27;</span>, <span class="string">&#x27;wx&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="variable language_">console</span>.<span class="title function_">err</span>(err); &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 最好将当前进程号写进文件锁中</span></span><br><span class="line"><span class="comment">// 当有异常的时候就知道最后这个锁的进程</span></span><br><span class="line">fs.<span class="title function_">writeFile</span>(</span><br><span class="line">  <span class="string">&#x27;config.lock&#x27;</span>,</span><br><span class="line">  process.<span class="property">pid</span>,</span><br><span class="line">  &#123; <span class="attr">flogs</span>: <span class="string">&#x27;wx&#x27;</span> &#125;,</span><br><span class="line">  <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="variable language_">console</span>.<span class="title function_">error</span>(err) &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<h3 id="mkdir-文件锁"><a href="#mkdir-文件锁" class="headerlink" title="mkdir 文件锁"></a>mkdir 文件锁</h3><p>独占标记有个问题，可能有些系统不能识别 <code>0_EXCL</code> 标记。另一个方案是把锁文件换成一个目录，PID 可以写入目录中的一个文件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fs.<span class="title function_">mkidr</span>(<span class="string">&#x27;config.lock&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="variable language_">console</span>.<span class="title function_">error</span>(err); &#125;</span><br><span class="line">  fs.<span class="title function_">writeFile</span>(<span class="string">`/config.lock/<span class="subst">$&#123;process.pid&#125;</span>`</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="variable language_">console</span>.<span class="title function_">error</span>(err); &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="lock-模块实现"><a href="#lock-模块实现" class="headerlink" title="lock 模块实现"></a>lock 模块实现</h3><p><a target="_blank" rel="noopener" href="https://github.com/npm/lockfile">https://github.com/npm/lockfile</a></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> lockDir = <span class="string">&#x27;config.lock&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> hasLock = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">lock</span> = <span class="keyword">function</span> (<span class="params">cb</span>) &#123; <span class="comment">// 获取锁</span></span><br><span class="line">  <span class="keyword">if</span> (hasLock) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(); &#125; <span class="comment">// 已经获取了一个锁</span></span><br><span class="line">  fs.<span class="title function_">mkdir</span>(lockDir, <span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(err); &#125; <span class="comment">// 无法创建锁</span></span><br><span class="line"></span><br><span class="line">    fs.<span class="title function_">writeFile</span>(lockDir + <span class="string">&#x27;/&#x27;</span> + process.<span class="property">pid</span>, <span class="keyword">function</span> (<span class="params">err</span>) &#123; <span class="comment">// 把 PID写入到目录中以便调试</span></span><br><span class="line">      <span class="keyword">if</span> (err) &#123; <span class="variable language_">console</span>.<span class="title function_">error</span>(err); &#125; <span class="comment">// 无法写入 PID，继续运行</span></span><br><span class="line">      hasLock = <span class="literal">true</span>; <span class="comment">// 锁创建了</span></span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">cb</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">unlock</span> = <span class="keyword">function</span> (<span class="params">cb</span>) &#123; <span class="comment">// 解锁方法</span></span><br><span class="line">  <span class="keyword">if</span> (!hasLock) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(); &#125; <span class="comment">// 如果没有需要解开的锁</span></span><br><span class="line">  fs.<span class="title function_">unlink</span>(lockDir + <span class="string">&#x27;/&#x27;</span> + process.<span class="property">pid</span>, <span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(err); &#125;</span><br><span class="line"></span><br><span class="line">    fs.<span class="title function_">rmdir</span>(lockDir, <span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) <span class="keyword">return</span> <span class="title function_">cb</span>(err);</span><br><span class="line">      hasLock = <span class="literal">false</span>;</span><br><span class="line">      <span class="title function_">cb</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (hasLock) &#123;</span><br><span class="line">    fs.<span class="title function_">unlinkSync</span>(lockDir + <span class="string">&#x27;/&#x27;</span> + process.<span class="property">pid</span>); <span class="comment">// 如果还有锁，在退出之前同步删除掉</span></span><br><span class="line">    fs.<span class="title function_">rmdirSync</span>(lockDir);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;removed lock&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="递归文件操作"><a href="#递归文件操作" class="headerlink" title="递归文件操作"></a>递归文件操作</h2><p>一个线上库：<a target="_blank" rel="noopener" href="https://github.com/substack/node-mkdirp">mkdirp</a></p>
<p>递归：要解决我们的问题就要先解决更小的相同的问题。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">dir-a</span><br><span class="line">├── dir-b</span><br><span class="line">│   ├── dir-c</span><br><span class="line">│   │   ├── dir-d</span><br><span class="line">│   │   │   └── file-e.png</span><br><span class="line">│   │   └── file-e.png</span><br><span class="line">│   ├── file-c.js</span><br><span class="line">│   └── file-d.txt</span><br><span class="line">├── file-a.js</span><br><span class="line">└── file-b.txt</span><br></pre></td></tr></table></figure>

<p>查找模块：<code>find /asset/dir-a -name=&quot;file.*&quot;</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">  <span class="string">&#x27;dir-a/dir-b/dir-c/dir-d/file-e.png&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;dir-a/dir-b/dir-c/file-e.png&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;dir-a/dir-b/file-c.js&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;dir-a/dir-b/file-d.txt&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;dir-a/file-a.js&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;dir-a/file-b.txt&#x27;</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> join = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>).<span class="property">join</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步查找</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">findSync</span> = <span class="keyword">function</span> (<span class="params">nameRe, startPath</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> results = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">finder</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> files = fs.<span class="title function_">readdirSync</span>(path);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; files.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> fpath = <span class="title function_">join</span>(path, files[i]);</span><br><span class="line">      <span class="keyword">const</span> stats = fs.<span class="title function_">statSync</span>(fpath);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (stats.<span class="title function_">isDirectory</span>()) &#123; <span class="title function_">finder</span>(fpath); &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (stats.<span class="title function_">isFile</span>() &amp;&amp; nameRe.<span class="title function_">test</span>(files[i])) &#123;</span><br><span class="line">        results.<span class="title function_">push</span>(fpath);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">finder</span>(startPath);</span><br><span class="line">  <span class="keyword">return</span> results;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步查找</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">find</span> = <span class="keyword">function</span> (<span class="params">nameRe, startPath, cb</span>) &#123; <span class="comment">// cb 可以传入 console.log，灵活</span></span><br><span class="line">  <span class="keyword">const</span> results = [];</span><br><span class="line">  <span class="keyword">let</span> asyncOps = <span class="number">0</span>; <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">finder</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    asyncOps++;</span><br><span class="line">    fs.<span class="title function_">readdir</span>(path, <span class="keyword">function</span> (<span class="params">er, files</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (er) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(er); &#125;</span><br><span class="line"></span><br><span class="line">      files.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">file</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> fpath = <span class="title function_">join</span>(path, file);</span><br><span class="line"></span><br><span class="line">        asyncOps++;</span><br><span class="line">        fs.<span class="title function_">stat</span>(fpath, <span class="keyword">function</span> (<span class="params">er, stats</span>) &#123;</span><br><span class="line">          <span class="keyword">if</span> (er) &#123; <span class="keyword">return</span> <span class="title function_">cb</span>(er); &#125;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (stats.<span class="title function_">isDirectory</span>()) <span class="title function_">finder</span>(fpath);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (stats.<span class="title function_">isFile</span>() &amp;&amp; nameRe.<span class="title function_">test</span>(file)) &#123;</span><br><span class="line">            results.<span class="title function_">push</span>(fpath);</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          asyncOps--;</span><br><span class="line">          <span class="keyword">if</span> (asyncOps == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="title function_">cb</span>(<span class="literal">null</span>, results);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      asyncOps--;</span><br><span class="line">      <span class="keyword">if</span> (asyncOps == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="title function_">cb</span>(<span class="literal">null</span>, results);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">finder</span>(startPath);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="built_in">exports</span>.<span class="title function_">findSync</span>(<span class="regexp">/file.*/</span>, <span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/dir-a`</span>));</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="built_in">exports</span>.<span class="title function_">find</span>(<span class="regexp">/file.*/</span>, <span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/dir-a`</span>, <span class="variable language_">console</span>.<span class="property">log</span>));</span><br></pre></td></tr></table></figure>

<h2 id="监视文件和文件夹"><a href="#监视文件和文件夹" class="headerlink" title="监视文件和文件夹"></a>监视文件和文件夹</h2><p>想要监听一个文件或者目录，并在文件更改后执行一个动作。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line">fs.<span class="title function_">watch</span>(<span class="string">&#x27;./watchdir&#x27;</span>, <span class="variable language_">console</span>.<span class="property">log</span>); <span class="comment">// 稳定且快</span></span><br><span class="line">fs.<span class="title function_">watchFile</span>(<span class="string">&#x27;./watchdir&#x27;</span>, <span class="variable language_">console</span>.<span class="property">log</span>); <span class="comment">// 跨平台</span></span><br></pre></td></tr></table></figure>

<h2 id="逐行地读取文件流"><a href="#逐行地读取文件流" class="headerlink" title="逐行地读取文件流"></a>逐行地读取文件流</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> readline = <span class="built_in">require</span>(<span class="string">&#x27;readline&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> rl = readline.<span class="title function_">createInterface</span>(&#123;</span><br><span class="line">  <span class="attr">input</span>: fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;/etc/hosts&#x27;</span>),</span><br><span class="line">  <span class="attr">crlfDelay</span>: <span class="title class_">Infinity</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">rl.<span class="title function_">on</span>(<span class="string">&#x27;line&#x27;</span>, <span class="function">(<span class="params">line</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`cc <span class="subst">$&#123;line&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">const</span> extract = line.<span class="title function_">match</span>(<span class="regexp">/(\d+\.\d+\.\d+\.\d+) (.*)/</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h1 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h1><h2 id="获取本地-IP"><a href="#获取本地-IP" class="headerlink" title="获取本地 IP"></a>获取本地 IP</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">get_local_ip</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> interfaces = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>).<span class="title function_">networkInterfaces</span>();</span><br><span class="line">  <span class="keyword">let</span> <span class="title class_">IPAdress</span> = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> devName <span class="keyword">in</span> interfaces) &#123;</span><br><span class="line">    <span class="keyword">const</span> iface = interfaces[devName];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; iface.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> alias = iface[i];</span><br><span class="line">      <span class="keyword">if</span> (alias.<span class="property">family</span> === <span class="string">&#x27;IPv4&#x27;</span> &amp;&amp; alias.<span class="property">address</span> !== <span class="string">&#x27;127.0.0.1&#x27;</span> &amp;&amp; !alias.<span class="property">internal</span>) &#123;</span><br><span class="line">        <span class="title class_">IPAdress</span> = alias.<span class="property">address</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">IPAdress</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="TCP-客户端"><a href="#TCP-客户端" class="headerlink" title="TCP 客户端"></a>TCP 客户端</h2><p>NodeJS 使用 <code>net</code> 模块创建 TCP 连接和服务。</p>
<h3 id="启动与测试-TCP"><a href="#启动与测试-TCP" class="headerlink" title="启动与测试 TCP"></a>启动与测试 TCP</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> clients = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> expectedAssertions = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = net.<span class="title function_">createServer</span>(<span class="keyword">function</span> (<span class="params">client</span>) &#123;</span><br><span class="line">  clients++;</span><br><span class="line">  <span class="keyword">const</span> clientId = clients;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Client connected:&#x27;</span>, clientId);</span><br><span class="line"></span><br><span class="line">  client.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Client disconnected:&#x27;</span>, clientId);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  client.<span class="title function_">write</span>(<span class="string">&#x27;Welcome client: &#x27;</span> + clientId);</span><br><span class="line">  client.<span class="title function_">pipe</span>(client);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">8000</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Server started on port 8000&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">runTest</span>(<span class="number">1</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">runTest</span>(<span class="number">2</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Tests finished&#x27;</span>);</span><br><span class="line">      assert.<span class="title function_">equal</span>(<span class="number">0</span>, expectedAssertions);</span><br><span class="line">      server.<span class="title function_">close</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">runTest</span>(<span class="params">expectedId, done</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> client = net.<span class="title function_">connect</span>(<span class="number">8000</span>);</span><br><span class="line"></span><br><span class="line">  client.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> expected = <span class="string">&#x27;Welcome client: &#x27;</span> + expectedId;</span><br><span class="line">    assert.<span class="title function_">equal</span>(data.<span class="title function_">toString</span>(), expected);</span><br><span class="line">    expectedAssertions--;</span><br><span class="line">    client.<span class="title function_">end</span>();</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  client.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, done);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="UDP-客户端"><a href="#UDP-客户端" class="headerlink" title="UDP 客户端"></a>UDP 客户端</h2><p>利用 <code>dgram</code> 模块创建数据报 <code>socket</code>，然后利用 <code>socket.send</code> 发送数据。</p>
<h3 id="文件发送服务"><a href="#文件发送服务" class="headerlink" title="文件发送服务"></a>文件发送服务</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dgram = <span class="built_in">require</span>(<span class="string">&#x27;dgram&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">41230</span>;</span><br><span class="line"><span class="keyword">const</span> defaultSize = <span class="number">16</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Client</span>(<span class="params">remoteIP</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inStream = fs.<span class="title function_">createReadStream</span>(__filename); <span class="comment">// 从当前文件创建可读流</span></span><br><span class="line">  <span class="keyword">const</span> socket = dgram.<span class="title function_">createSocket</span>(<span class="string">&#x27;udp4&#x27;</span>); <span class="comment">// 创建新的数据流 socket 作为客户端</span></span><br><span class="line"></span><br><span class="line">  inStream.<span class="title function_">on</span>(<span class="string">&#x27;readable&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">sendData</span>(); <span class="comment">// 当可读流准备好，开始发送数据到服务器</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">sendData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> message = inStream.<span class="title function_">read</span>(defaultSize); <span class="comment">// 读取数据块</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!message) &#123;</span><br><span class="line">      <span class="keyword">return</span> socket.<span class="title function_">unref</span>(); <span class="comment">// 客户端完成任务后，使用 unref 安全关闭它</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送数据到服务器</span></span><br><span class="line">    socket.<span class="title function_">send</span>(message, <span class="number">0</span>, message.<span class="property">length</span>, port, remoteIP, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="title function_">sendData</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Server</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> socket = dgram.<span class="title function_">createSocket</span>(<span class="string">&#x27;udp4&#x27;</span>); <span class="comment">// 创建一个 socket 提供服务</span></span><br><span class="line"></span><br><span class="line">  socket.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">msg</span>) &#123;</span><br><span class="line">    process.<span class="property">stdout</span>.<span class="title function_">write</span>(msg.<span class="title function_">toString</span>());</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  socket.<span class="title function_">on</span>(<span class="string">&#x27;listening&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Server ready:&#x27;</span>, socket.<span class="title function_">address</span>());</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  socket.<span class="title function_">bind</span>(port);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (process.<span class="property">argv</span>[<span class="number">2</span>] === <span class="string">&#x27;client&#x27;</span>) &#123; <span class="comment">// 根据命令行选项确定运行客户端还是服务端</span></span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Client</span>(process.<span class="property">argv</span>[<span class="number">3</span>]);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Server</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="HTTP-客户端"><a href="#HTTP-客户端" class="headerlink" title="HTTP 客户端"></a>HTTP 客户端</h2><p>使用 <code>http.createServer</code> 和 <code>http.createClient</code> 运行 HTTP 服务。</p>
<h3 id="启动与测试-HTTP"><a href="#启动与测试-HTTP" class="headerlink" title="启动与测试 HTTP"></a>启动与测试 HTTP</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span> &#125;); <span class="comment">// 写入基于文本的响应头</span></span><br><span class="line">  res.<span class="title function_">write</span>(<span class="string">&#x27;Hello, world.&#x27;</span>); <span class="comment">// 发送消息回客户端</span></span><br><span class="line">  res.<span class="title function_">end</span>();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">8000</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Listening on port 8000&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> req = http.<span class="title function_">request</span>(&#123; <span class="attr">port</span>: <span class="number">8000</span>&#125;, <span class="keyword">function</span>(<span class="params">res</span>) &#123; <span class="comment">// 创建请求</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;HTTP headers:&#x27;</span>, res.<span class="property">headers</span>);</span><br><span class="line">  res.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123; <span class="comment">// 给 data 事件创建监听，确保和期望值一致</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Body:&#x27;</span>, data.<span class="title function_">toString</span>());</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="string">&#x27;Hello, world.&#x27;</span>, data.<span class="title function_">toString</span>());</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="number">200</span>, res.<span class="property">statusCode</span>);</span><br><span class="line">    server.<span class="title function_">unref</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;测试完成&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">req.<span class="title function_">end</span>();</span><br></pre></td></tr></table></figure>

<h3 id="重定向"><a href="#重定向" class="headerlink" title="重定向"></a>重定向</h3><p>HTTP 标准定义了标识重定向发生时的状态码，它也指出了客户端应该检查无限循环。</p>
<ul>
<li>300：多重选择</li>
<li>301：永久移动到新位置</li>
<li>302：找到重定向跳转</li>
<li>303：参见其他信息</li>
<li>304：没有改动</li>
<li>305：使用代理</li>
<li>307：临时重定向</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> https = <span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> url = <span class="built_in">require</span>(<span class="string">&#x27;url&#x27;</span>); <span class="comment">// 有很多接续 URLs 的方法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数被用来创建一个对象来构成请求对象的声明周期</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Request</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">maxRedirects</span> = <span class="number">10</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">redirects</span> = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Request</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">get</span> = <span class="keyword">function</span>(<span class="params">href, callback</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> uri = url.<span class="title function_">parse</span>(href); <span class="comment">// 解析 URLs 成为 Node http 模块使用的格式，确定是否使用 HTTPS</span></span><br><span class="line">  <span class="keyword">const</span> options = &#123; <span class="attr">host</span>: uri.<span class="property">host</span>, <span class="attr">path</span>: uri.<span class="property">path</span> &#125;;</span><br><span class="line">  <span class="keyword">const</span> httpGet = uri.<span class="property">protocol</span> === <span class="string">&#x27;http:&#x27;</span> ? http.<span class="property">get</span> : https.<span class="property">get</span>;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;GET:&#x27;</span>, href);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">processResponse</span>(<span class="params">response</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (response.<span class="property">statusCode</span> &gt;= <span class="number">300</span> &amp;&amp; response.<span class="property">statusCode</span> &lt; <span class="number">400</span>) &#123; <span class="comment">// 检查状态码是否在 HTTP 重定向范围</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">redirects</span> &gt;= <span class="variable language_">this</span>.<span class="property">maxRedirects</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">error</span> = <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Too many redirects for: &#x27;</span> + href);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">redirects</span>++; <span class="comment">// 重定向计数自增</span></span><br><span class="line">        href = url.<span class="title function_">resolve</span>(options.<span class="property">host</span>, response.<span class="property">headers</span>.<span class="property">location</span>); <span class="comment">// 使用 url.resolve 确保相对路径的 URLs 转换为绝对路径 URLs</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">get</span>(href, callback);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    response.<span class="property">url</span> = href;</span><br><span class="line">    response.<span class="property">redirects</span> = <span class="variable language_">this</span>.<span class="property">redirects</span>;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Redirected:&#x27;</span>, href);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">end</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Connection ended&#x27;</span>);</span><br><span class="line">      <span class="title function_">callback</span>(<span class="variable language_">this</span>.<span class="property">error</span>, response);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    response.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Got data, length:&#x27;</span>, data.<span class="property">length</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    response.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, end.<span class="title function_">bind</span>(<span class="variable language_">this</span>)); <span class="comment">// 绑定回调到 Request 实例，确保能拿到实例属性</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">httpGet</span>(options, processResponse.<span class="title function_">bind</span>(<span class="variable language_">this</span>))</span><br><span class="line">    .<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">      <span class="title function_">callback</span>(err);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> request = <span class="keyword">new</span> <span class="title class_">Request</span>();</span><br><span class="line">request.<span class="title function_">get</span>(<span class="string">&#x27;http://google.com/&#x27;</span>, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`</span></span><br><span class="line"><span class="string">      Fetched URL: <span class="subst">$&#123;res.url&#125;</span> with <span class="subst">$&#123;res.redirects&#125;</span> redirects</span></span><br><span class="line"><span class="string">    `</span>);</span><br><span class="line">    process.<span class="title function_">exit</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="HTTP-代理"><a href="#HTTP-代理" class="headerlink" title="HTTP 代理"></a>HTTP 代理</h3><ul>
<li>ISP 使用透明代理使网络更加高效</li>
<li>使用缓存代理服务器减少宽带</li>
<li>Web 应用程序的 DevOps 利用他们提升应用程序性能</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> url = <span class="built_in">require</span>(<span class="string">&#x27;url&#x27;</span>);</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">req, res</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;start request:&#x27;</span>, req.<span class="property">url</span>);</span><br><span class="line">  <span class="keyword">const</span> options = url.<span class="title function_">parse</span>(req.<span class="property">url</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(options);</span><br><span class="line">  options.<span class="property">headers</span> = req.<span class="property">headers</span>;</span><br><span class="line">  <span class="keyword">const</span> proxyRequest = http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">proxyResponse</span>) &#123; <span class="comment">// 创建请求来复制原始的请求</span></span><br><span class="line">    proxyResponse.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) &#123; <span class="comment">// 监听数据，返回给浏览器</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;proxyResponse length:&#x27;</span>, chunk.<span class="property">length</span>);</span><br><span class="line">      res.<span class="title function_">write</span>(chunk, <span class="string">&#x27;binary&#x27;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    proxyResponse.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="comment">// 追踪代理请求完成</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;proxied request ended&#x27;</span>);</span><br><span class="line">      res.<span class="title function_">end</span>();</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    res.<span class="title function_">writeHead</span>(proxyResponse.<span class="property">statusCode</span>, proxyResponse.<span class="property">headers</span>); <span class="comment">// 发送头部信息给服务器</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  req.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) &#123; <span class="comment">// 捕获从浏览器发送到服务器的数据</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;in request length:&#x27;</span>, chunk.<span class="property">length</span>);</span><br><span class="line">    proxyRequest.<span class="title function_">write</span>(chunk, <span class="string">&#x27;binary&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  req.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="comment">// 追踪原始的请求什么时候结束</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;original request ended&#x27;</span>);</span><br><span class="line">    proxyRequest.<span class="title function_">end</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8888</span>); <span class="comment">// 监听来自本地浏览器的连接</span></span><br></pre></td></tr></table></figure>

<h3 id="封装-request-promise"><a href="#封装-request-promise" class="headerlink" title="封装 request-promise"></a>封装 request-promise</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> https = <span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> promisify = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>).<span class="property">promisify</span>;</span><br><span class="line"></span><br><span class="line">https.<span class="property">get</span>[promisify.<span class="property">custom</span>] = <span class="keyword">function</span> <span class="title function_">getAsync</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    https.<span class="title function_">get</span>(options, <span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">      response.<span class="property">end</span> = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> response.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, resolve));</span><br><span class="line">      <span class="title function_">resolve</span>(response);</span><br><span class="line">    &#125;).<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, reject);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> rp = <span class="title function_">promisify</span>(https.<span class="property">get</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">rp</span>(<span class="string">&#x27;https://jsonmock.hackerrank.com/api/movies/search/?Title=Spiderman&amp;page=1&#x27;</span>);</span><br><span class="line">  <span class="keyword">let</span> body = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  res.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">chunk</span>) =&gt;</span> body += chunk);</span><br><span class="line">  <span class="keyword">await</span> res.<span class="property">end</span>;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(body);</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure>

<h2 id="DNS-请求"><a href="#DNS-请求" class="headerlink" title="DNS 请求"></a>DNS 请求</h2><p>使用 <code>dns</code> 模块创建 DNS 请求。</p>
<ul>
<li>A：<code>dns.resolve</code>，A 记录存储 IP 地址</li>
<li>TXT：<code>dns.resulveTxt</code>，文本值可以用于在 DNS 上构建其他服务</li>
<li>SRV：<code>dns.resolveSrv</code>，服务记录定义服务的定位数据，通常包含主机名和端口号</li>
<li>NS：<code>dns.resolveNs</code>，指定域名服务器</li>
<li>CNAME：<code>dns.resolveCname</code>，相关的域名记录，设置为域名而不是 IP 地址</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dns = <span class="built_in">require</span>(<span class="string">&#x27;dns&#x27;</span>);</span><br><span class="line"></span><br><span class="line">dns.<span class="title function_">resolve</span>(<span class="string">&#x27;www.chenng.cn&#x27;</span>, <span class="keyword">function</span> (<span class="params">err, addresses</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Addresses:&#x27;</span>, addresses);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="crypto-库加密解密"><a href="#crypto-库加密解密" class="headerlink" title="crypto 库加密解密"></a>crypto 库加密解密</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">aesEncrypt</span>(<span class="params">data, key = <span class="string">&#x27;key&#x27;</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cipher = crypto.<span class="title function_">createCipher</span>(<span class="string">&#x27;aes192&#x27;</span>, key)</span><br><span class="line">  <span class="keyword">let</span> crypted = cipher.<span class="title function_">update</span>(data, <span class="string">&#x27;utf8&#x27;</span>, <span class="string">&#x27;hex&#x27;</span>)</span><br><span class="line">  crypted += cipher.<span class="title function_">final</span>(<span class="string">&#x27;hex&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> crypted</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">aesDecrypt</span>(<span class="params">encrypted, key = <span class="string">&#x27;key&#x27;</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> decipher = crypto.<span class="title function_">createDecipher</span>(<span class="string">&#x27;aes192&#x27;</span>, key)</span><br><span class="line">  <span class="keyword">let</span> decrypted = decipher.<span class="title function_">update</span>(encrypted, <span class="string">&#x27;hex&#x27;</span>, <span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line">  decrypted += decipher.<span class="title function_">final</span>(<span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> decrypted</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="发起-HTTP-请求的方法"><a href="#发起-HTTP-请求的方法" class="headerlink" title="发起 HTTP 请求的方法"></a>发起 HTTP 请求的方法</h2><ul>
<li>HTTP 标准库<ul>
<li>无需安装外部依赖</li>
<li>需要以块为单位接受数据，自己监听 end 事件</li>
<li>HTTP 和 HTTPS 是两个模块，需要区分使用</li>
</ul>
</li>
<li>Request 库<ul>
<li>使用方便</li>
<li>有 promise 版本 <code>request-promise</code></li>
</ul>
</li>
<li>Axios<ul>
<li>既可以用在浏览器又可以用在 NodeJS</li>
<li>可以使用 axios.all 并发多个请求</li>
</ul>
</li>
<li>SuperAgent<ul>
<li>可以链式使用</li>
</ul>
</li>
<li>node-fetch<ul>
<li>浏览器的 fetch 移植过来的</li>
</ul>
</li>
</ul>
<h1 id="子进程"><a href="#子进程" class="headerlink" title="子进程"></a>子进程</h1><h2 id="执行外部应用"><a href="#执行外部应用" class="headerlink" title="执行外部应用"></a>执行外部应用</h2><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><ul>
<li>4个异步方法：exec、execFile、fork、spawn<ul>
<li>Node<ul>
<li>fork：想将一个 Node 进程作为一个独立的进程来运行的时候使用，是的计算处理和文件描述器脱离 Node 主进程</li>
</ul>
</li>
<li>非 Node<ul>
<li>spawn：处理一些会有很多子进程 I&#x2F;O 时、进程会有大量输出时使用</li>
<li>execFile：只需执行一个外部程序的时候使用，执行速度快，处理用户输入相对安全</li>
<li>exec：想直接访问线程的 shell 命令时使用，一定要注意用户输入</li>
</ul>
</li>
</ul>
</li>
<li>3个同步方法：execSync、execFileSync、spawnSync</li>
<li>通过 API 创建出来的子进程和父进程没有任何必然联系</li>
</ul>
<h3 id="execFile"><a href="#execFile" class="headerlink" title="execFile"></a>execFile</h3><ul>
<li>会把输出结果缓存好，通过回调返回最后结果或者异常信息</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"></span><br><span class="line">cp.<span class="title function_">execFile</span>(<span class="string">&#x27;echo&#x27;</span>, [<span class="string">&#x27;hello&#x27;</span>, <span class="string">&#x27;world&#x27;</span>], <span class="function">(<span class="params">err, stdout, stderr</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123; <span class="variable language_">console</span>.<span class="title function_">error</span>(err); &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stdout: &#x27;</span>, stdout);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stderr: &#x27;</span>, stderr);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="spawn"><a href="#spawn" class="headerlink" title="spawn"></a>spawn</h3><ul>
<li>通过流可以使用有大量数据输出的外部应用，节约内存</li>
<li>使用流提高数据响应效率</li>
<li>spawn 方法返回一个 I&#x2F;O 的流接口</li>
</ul>
<h4 id="单一任务"><a href="#单一任务" class="headerlink" title="单一任务"></a>单一任务</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child = cp.<span class="title function_">spawn</span>(<span class="string">&#x27;echo&#x27;</span>, [<span class="string">&#x27;hello&#x27;</span>, <span class="string">&#x27;world&#x27;</span>]);</span><br><span class="line">child.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="variable language_">console</span>.<span class="property">error</span>);</span><br><span class="line">child.<span class="property">stdout</span>.<span class="title function_">pipe</span>(process.<span class="property">stdout</span>);</span><br><span class="line">child.<span class="property">stderr</span>.<span class="title function_">pipe</span>(process.<span class="property">stderr</span>);</span><br></pre></td></tr></table></figure>

<h4 id="多任务串联"><a href="#多任务串联" class="headerlink" title="多任务串联"></a>多任务串联</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cat = cp.<span class="title function_">spawn</span>(<span class="string">&#x27;cat&#x27;</span>, [path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;messy.txt&#x27;</span>)]);</span><br><span class="line"><span class="keyword">const</span> sort = cp.<span class="title function_">spawn</span>(<span class="string">&#x27;sort&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> uniq = cp.<span class="title function_">spawn</span>(<span class="string">&#x27;uniq&#x27;</span>);</span><br><span class="line"></span><br><span class="line">cat.<span class="property">stdout</span>.<span class="title function_">pipe</span>(sort.<span class="property">stdin</span>);</span><br><span class="line">sort.<span class="property">stdout</span>.<span class="title function_">pipe</span>(uniq.<span class="property">stdin</span>);</span><br><span class="line">uniq.<span class="property">stdout</span>.<span class="title function_">pipe</span>(process.<span class="property">stdout</span>);</span><br></pre></td></tr></table></figure>

<h3 id="exec"><a href="#exec" class="headerlink" title="exec"></a>exec</h3><ul>
<li>只有一个字符串命令</li>
<li>和 shell 一模一样</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"></span><br><span class="line">cp.<span class="title function_">exec</span>(<span class="string">`cat <span class="subst">$&#123;__dirname&#125;</span>/messy.txt | sort | uniq`</span>, <span class="function">(<span class="params">err, stdout, stderr</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(stdout);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="fork"><a href="#fork" class="headerlink" title="fork"></a>fork</h3><ul>
<li>fork 方法会开发一个 IPC 通道，不同的 Node 进程进行消息传送</li>
<li>一个子进程消耗 30ms 启动时间和 10MB 内存</li>
<li>子进程：<code>process.on(&#39;message&#39;)</code>、<code>process.send()</code></li>
<li>父进程：<code>child.on(&#39;message&#39;)</code>、<code>child.send()</code></li>
</ul>
<h4 id="父子通信"><a href="#父子通信" class="headerlink" title="父子通信"></a>父子通信</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// parent.js</span></span><br><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child = cp.<span class="title function_">fork</span>(<span class="string">&#x27;./child&#x27;</span>, &#123; <span class="attr">silent</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">child.<span class="title function_">send</span>(<span class="string">&#x27;monkeys&#x27;</span>);</span><br><span class="line">child.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">message</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;got message from child&#x27;</span>, message, <span class="keyword">typeof</span> message);</span><br><span class="line">&#125;)</span><br><span class="line">child.<span class="property">stdout</span>.<span class="title function_">pipe</span>(process.<span class="property">stdout</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  child.<span class="title function_">disconnect</span>();</span><br><span class="line">&#125;, <span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// child.js</span></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">message</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;got one&#x27;</span>, message);</span><br><span class="line">  process.<span class="title function_">send</span>(<span class="string">&#x27;no pizza&#x27;</span>);</span><br><span class="line">  process.<span class="title function_">send</span>(<span class="number">1</span>);</span><br><span class="line">  process.<span class="title function_">send</span>(&#123; <span class="attr">my</span>: <span class="string">&#x27;object&#x27;</span> &#125;);</span><br><span class="line">  process.<span class="title function_">send</span>(<span class="literal">false</span>);</span><br><span class="line">  process.<span class="title function_">send</span>(<span class="literal">null</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(process);</span><br></pre></td></tr></table></figure>

<h2 id="常用技巧"><a href="#常用技巧" class="headerlink" title="常用技巧"></a>常用技巧</h2><h3 id="退出时杀死所有子进程"><a href="#退出时杀死所有子进程" class="headerlink" title="退出时杀死所有子进程"></a>退出时杀死所有子进程</h3><ul>
<li>保留对由 spawn 返回的 ChildProcess 对象的引用，并在退出主进程时将其杀死</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> spawn = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">spawn</span>;</span><br><span class="line"><span class="keyword">const</span> children = [];</span><br><span class="line"></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;killing&#x27;</span>, children.<span class="property">length</span>, <span class="string">&#x27;child processes&#x27;</span>);</span><br><span class="line">  children.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">child</span>) &#123;</span><br><span class="line">    child.<span class="title function_">kill</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">children.<span class="title function_">push</span>(<span class="title function_">spawn</span>(<span class="string">&#x27;/bin/sleep&#x27;</span>, [<span class="string">&#x27;10&#x27;</span>]));</span><br><span class="line">children.<span class="title function_">push</span>(<span class="title function_">spawn</span>(<span class="string">&#x27;/bin/sleep&#x27;</span>, [<span class="string">&#x27;10&#x27;</span>]));</span><br><span class="line">children.<span class="title function_">push</span>(<span class="title function_">spawn</span>(<span class="string">&#x27;/bin/sleep&#x27;</span>, [<span class="string">&#x27;10&#x27;</span>]));</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123; process.<span class="title function_">exit</span>(<span class="number">0</span>); &#125;, <span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<h2 id="Cluster-的理解"><a href="#Cluster-的理解" class="headerlink" title="Cluster 的理解"></a>Cluster 的理解</h2><ul>
<li>解决 NodeJS 单进程无法充分利用多核 CPU 问题</li>
<li>通过 master-cluster 模式可以使得应用更加健壮</li>
<li>Cluster 底层是 child_process 模块，除了可以发送普通消息，还可以发送底层对象 <code>TCP</code>、<code>UDP</code> 等</li>
<li>TCP 主进程发送到子进程，子进程能根据消息重建出 TCP 连接，Cluster 可以决定 fork 出合适的硬件资源的子进程数</li>
</ul>
<h1 id="Node-多线程"><a href="#Node-多线程" class="headerlink" title="Node 多线程"></a>Node 多线程</h1><h2 id="单线程问题"><a href="#单线程问题" class="headerlink" title="单线程问题"></a>单线程问题</h2><ul>
<li>对 cpu 利用不足</li>
<li>某个未捕获的异常可能会导致整个程序的退出</li>
</ul>
<h2 id="Node-线程"><a href="#Node-线程" class="headerlink" title="Node 线程"></a>Node 线程</h2><ul>
<li>Node 进程占用了 7 个线程</li>
<li>Node 中最核心的是 v8 引擎，在 Node 启动后，会创建 v8 的实例，这个实例是多线程的<ul>
<li>主线程：编译、执行代码</li>
<li>编译&#x2F;优化线程：在主线程执行的时候，可以优化代码</li>
<li>分析器线程：记录分析代码运行时间，为 Crankshaft 优化代码执行提供依据</li>
<li>垃圾回收的几个线程</li>
</ul>
</li>
<li>JavaScript 的执行是单线程的，但 Javascript 的宿主环境，无论是 Node 还是浏览器都是多线程的</li>
</ul>
<h2 id="异步-IO"><a href="#异步-IO" class="headerlink" title="异步 IO"></a>异步 IO</h2><ul>
<li>Node 中有一些 IO 操作（DNS，FS）和一些 CPU 密集计算（Zlib，Crypto）会启用 Node 的线程池</li>
<li>线程池默认大小为 4，可以手动更改线程池默认大小</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="property">env</span>.<span class="property">UV_THREADPOOL_SIZE</span> = <span class="number">64</span></span><br></pre></td></tr></table></figure>

<h2 id="cluster-多进程"><a href="#cluster-多进程" class="headerlink" title="cluster 多进程"></a>cluster 多进程</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cluster = <span class="built_in">require</span>(<span class="string">&#x27;cluster&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> numCPUs = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>).<span class="title function_">cpus</span>().<span class="property">length</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (cluster.<span class="property">isMaster</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`主进程 <span class="subst">$&#123;process.pid&#125;</span> 正在运行`</span>);</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; numCPUs; i++) &#123;</span><br><span class="line">    cluster.<span class="title function_">fork</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  cluster.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function">(<span class="params">worker, code, signal</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`工作进程 <span class="subst">$&#123;worker.process.pid&#125;</span> 已退出`</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">// 工作进程可以共享任何 TCP 连接。</span></span><br><span class="line">  <span class="comment">// 在本例子中，共享的是 HTTP 服务器。</span></span><br><span class="line">  http.<span class="title function_">createServer</span>(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">writeHead</span>(<span class="number">200</span>);</span><br><span class="line">    res.<span class="title function_">end</span>(<span class="string">&#x27;Hello World&#x27;</span>);</span><br><span class="line">  &#125;).<span class="title function_">listen</span>(<span class="number">8000</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`工作进程 <span class="subst">$&#123;process.pid&#125;</span> 已启动`</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>一共有 9 个进程，其中一个主进程，cpu 个数 x cpu 核数 &#x3D; 2 x 4 &#x3D; 8 个 子进程</li>
<li>无论 child_process 还是 cluster，都不是多线程模型，而是多进程模型</li>
<li>应对单线程问题，通常使用多进程的方式来模拟多线程</li>
</ul>
<h2 id="真-Node-多线程"><a href="#真-Node-多线程" class="headerlink" title="真 Node 多线程"></a>真 Node 多线程</h2><ul>
<li>Node 10.5.0 的发布，给出了一个实验性质的模块 worker_threads 给 Node 提供真正的多线程能力</li>
<li>worker_thread 模块中有 4 个对象和 2 个类<ul>
<li>isMainThread: 是否是主线程，源码中是通过 threadId &#x3D;&#x3D;&#x3D; 0 进行判断的。</li>
<li>MessagePort: 用于线程之间的通信，继承自 EventEmitter。</li>
<li>MessageChannel: 用于创建异步、双向通信的通道实例。</li>
<li>threadId: 线程 ID。</li>
<li>Worker: 用于在主线程中创建子线程。第一个参数为 filename，表示子线程执行的入口。</li>
<li>parentPort: 在 worker 线程里是表示父进程的 MessagePort 类型的对象，在主线程里为 null</li>
<li>workerData: 用于在主进程中向子进程传递数据（data 副本）</li>
</ul>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  isMainThread,</span><br><span class="line">  parentPort,</span><br><span class="line">  workerData,</span><br><span class="line">  threadId,</span><br><span class="line">  <span class="title class_">MessageChannel</span>,</span><br><span class="line">  <span class="title class_">MessagePort</span>,</span><br><span class="line">  <span class="title class_">Worker</span></span><br><span class="line">&#125; = <span class="built_in">require</span>(<span class="string">&#x27;worker_threads&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">mainThread</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> worker = <span class="keyword">new</span> <span class="title class_">Worker</span>(__filename, &#123; <span class="attr">workerData</span>: i &#125;);</span><br><span class="line">    worker.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function"><span class="params">code</span> =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`main: worker stopped with exit code <span class="subst">$&#123;code&#125;</span>`</span>); &#125;);</span><br><span class="line">    worker.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`main: receive <span class="subst">$&#123;msg&#125;</span>`</span>);</span><br><span class="line">      worker.<span class="title function_">postMessage</span>(msg + <span class="number">1</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">workerThread</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`worker: workerDate <span class="subst">$&#123;workerData&#125;</span>`</span>);</span><br><span class="line">  parentPort.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`worker: receive <span class="subst">$&#123;msg&#125;</span>`</span>);</span><br><span class="line">  &#125;),</span><br><span class="line">  parentPort.<span class="title function_">postMessage</span>(workerData);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">  <span class="title function_">mainThread</span>();</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">workerThread</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="线程通信"><a href="#线程通信" class="headerlink" title="线程通信"></a>线程通信</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="title class_">Worker</span>,</span><br><span class="line">  <span class="title class_">MessageChannel</span>,</span><br><span class="line">  <span class="title class_">MessagePort</span>,</span><br><span class="line">  isMainThread,</span><br><span class="line">  parentPort</span><br><span class="line">&#125; = <span class="built_in">require</span>(<span class="string">&#x27;worker_threads&#x27;</span>);</span><br><span class="line"><span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">  <span class="keyword">const</span> worker = <span class="keyword">new</span> <span class="title class_">Worker</span>(__filename);</span><br><span class="line">  <span class="keyword">const</span> subChannel = <span class="keyword">new</span> <span class="title class_">MessageChannel</span>();</span><br><span class="line">  worker.<span class="title function_">postMessage</span>(&#123; <span class="attr">hereIsYourPort</span>: subChannel.<span class="property">port1</span> &#125;, [subChannel.<span class="property">port1</span>]);</span><br><span class="line">  subChannel.<span class="property">port2</span>.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;received:&#x27;</span>, value);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  parentPort.<span class="title function_">once</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">assert</span>(value.<span class="property">hereIsYourPort</span> <span class="keyword">instanceof</span> <span class="title class_">MessagePort</span>);</span><br><span class="line">    value.<span class="property">hereIsYourPort</span>.<span class="title function_">postMessage</span>(<span class="string">&#x27;the worker is sending this&#x27;</span>);</span><br><span class="line">    value.<span class="property">hereIsYourPort</span>.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="多进程-vs-多线程"><a href="#多进程-vs-多线程" class="headerlink" title="多进程 vs 多线程"></a>多进程 vs 多线程</h2><p>进程是资源分配的最小单位，线程是CPU调度的最小单位</p>
<h1 id="分布式"><a href="#分布式" class="headerlink" title="分布式"></a>分布式</h1><h2 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h2><ul>
<li>在单机场景下，可以使用语言的内置锁来实现进程同步</li>
<li>在分布式场景下，需要同步的进程可能位于不同的节点上，那么就需要使用分布式锁</li>
</ul>
<h3 id="数据库的唯一索引"><a href="#数据库的唯一索引" class="headerlink" title="数据库的唯一索引"></a>数据库的唯一索引</h3><p>获得锁时向表中插入一条记录，释放锁时删除这条记录。</p>
<ul>
<li>锁没有失效时间，解锁失败的话其它进程无法再获得该锁。</li>
<li>只能是非阻塞锁，插入失败直接就报错了，无法重试。</li>
<li>不可重入，已经获得锁的进程也必须重新获取锁。</li>
</ul>
<h3 id="Redis-的-SETNX-指令"><a href="#Redis-的-SETNX-指令" class="headerlink" title="Redis 的 SETNX 指令"></a>Redis 的 SETNX 指令</h3><p>使用 SETNX（set if not exist）指令插入一个键值对，如果 Key 已经存在，那么会返回 False，否则插入成功并返回 True</p>
<ul>
<li>SETNX 指令和数据库的唯一索引类似，保证了只存在一个 Key 的键值对，可以用一个 Key 的键值对是否存在来判断是否存于锁定状态</li>
<li>EXPIRE 指令可以为一个键值对设置一个过期时间，从而避免了数据库唯一索引实现方式中释放锁失败的问题</li>
</ul>
<h3 id="Redis-的-RedLock-算法"><a href="#Redis-的-RedLock-算法" class="headerlink" title="Redis 的 RedLock 算法"></a>Redis 的 RedLock 算法</h3><p>使用了多个 Redis 实例来实现分布式锁，这是为了保证在发生单点故障时仍然可用</p>
<ul>
<li>尝试从 N 个相互独立 Redis 实例获取锁</li>
<li>计算获取锁消耗的时间，只有当这个时间小于锁的过期时间，并且从大多数（N &#x2F; 2 + 1）实例上获取了锁，那么就认为锁获取成功了</li>
<li>如果锁获取失败，就到每个实例上释放锁</li>
</ul>
<h1 id="项目管理"><a href="#项目管理" class="headerlink" title="项目管理"></a>项目管理</h1><h2 id="组件式构建"><a href="#组件式构建" class="headerlink" title="组件式构建"></a>组件式构建</h2><p><a target="_blank" rel="noopener" href="https://github.com/i0natan/nodebestpractices/blob/master/sections/projectstructre/breakintcomponents.chinese.md">https://github.com/i0natan/nodebestpractices/blob/master/sections/projectstructre/breakintcomponents.chinese.md</a></p>
<h2 id="多环境配置"><a href="#多环境配置" class="headerlink" title="多环境配置"></a>多环境配置</h2><ul>
<li>JSON 配置文件</li>
<li>环境变量<br>使用第三方模块管理（nconf）</li>
</ul>
<h2 id="依赖管理"><a href="#依赖管理" class="headerlink" title="依赖管理"></a>依赖管理</h2><ul>
<li>dependencies：模块正常运行需要的依赖</li>
<li>devDependencies：开发时候需要的依赖</li>
<li>optionalDependencies：非必要依赖，某种程度上增强</li>
<li>peerDependencies：运行时依赖，限定版本</li>
</ul>
<h1 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h1><h2 id="处理未捕获的异常"><a href="#处理未捕获的异常" class="headerlink" title="处理未捕获的异常"></a>处理未捕获的异常</h2><ul>
<li>除非开发者记得添加.catch语句，在这些地方抛出的错误都不会被 uncaughtException 事件处理程序来处理，然后消失掉。</li>
<li>Node 应用不会奔溃，但可能导致内存泄露</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;uncaughtException&#x27;</span>, <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 我刚收到一个从未被处理的错误</span></span><br><span class="line">  <span class="comment">// 现在处理它，并决定是否需要重启应用</span></span><br><span class="line">  errorManagement.<span class="property">handler</span>.<span class="title function_">handleError</span>(error);</span><br><span class="line">  <span class="keyword">if</span> (!errorManagement.<span class="property">handler</span>.<span class="title function_">isTrustedError</span>(error)) &#123;</span><br><span class="line">    process.<span class="title function_">exit</span>(<span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;unhandledRejection&#x27;</span>, <span class="function">(<span class="params">reason, p</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 我刚刚捕获了一个未处理的promise rejection,</span></span><br><span class="line">  <span class="comment">// 因为我们已经有了对于未处理错误的后备的处理机制（见下面）</span></span><br><span class="line">  <span class="comment">// 直接抛出，让它来处理</span></span><br><span class="line">  <span class="keyword">throw</span> reason;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="通过-domain-管理异常"><a href="#通过-domain-管理异常" class="headerlink" title="通过 domain 管理异常"></a>通过 domain 管理异常</h2><ul>
<li>通过 domain 模块的 create 方法创建实例</li>
<li>某个错误已经任何其他错误都会被同一个 error 处理方法处理</li>
<li>任何在这个回调中导致错误的代码都会被 domain 覆盖到</li>
<li>允许我们代码在一个沙盒运行，并且可以使用 res 对象给用户反馈</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> domain = <span class="built_in">require</span>(<span class="string">&#x27;domain&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> audioDomain = domain.<span class="title function_">create</span>();</span><br><span class="line"></span><br><span class="line">audioDomain.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;audioDomain error:&#x27;</span>, err);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">audioDomain.<span class="title function_">run</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> musicPlayer = <span class="keyword">new</span> <span class="title class_">MusicPlayer</span>();</span><br><span class="line">  musicPlayer.<span class="title function_">play</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="Joi-验证参数"><a href="#Joi-验证参数" class="headerlink" title="Joi 验证参数"></a>Joi 验证参数</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> memberSchema = <span class="title class_">Joi</span>.<span class="title function_">object</span>().<span class="title function_">keys</span>(&#123;</span><br><span class="line"> <span class="attr">password</span>: <span class="title class_">Joi</span>.<span class="title function_">string</span>().<span class="title function_">regex</span>(<span class="regexp">/^[a-zA-Z0-9]&#123;3,30&#125;$/</span>),</span><br><span class="line"> <span class="attr">birthyear</span>: <span class="title class_">Joi</span>.<span class="title function_">number</span>().<span class="title function_">integer</span>().<span class="title function_">min</span>(<span class="number">1900</span>).<span class="title function_">max</span>(<span class="number">2013</span>),</span><br><span class="line"> <span class="attr">email</span>: <span class="title class_">Joi</span>.<span class="title function_">string</span>().<span class="title function_">email</span>(),</span><br><span class="line">&#125;);</span><br><span class="line"> </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">addNewMember</span>(<span class="params">newMember</span>) &#123;</span><br><span class="line"> <span class="comment">//assertions come first</span></span><br><span class="line"> <span class="title class_">Joi</span>.<span class="title function_">assert</span>(newMember, memberSchema); <span class="comment">//throws if validation fails</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//other logic here</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Kibana-系统监控"><a href="#Kibana-系统监控" class="headerlink" title="Kibana 系统监控"></a>Kibana 系统监控</h2><p><a target="_blank" rel="noopener" href="https://github.com/i0natan/nodebestpractices/blob/master/sections/production/smartlogging.chinese.md">https://github.com/i0natan/nodebestpractices/blob/master/sections/production/smartlogging.chinese.md</a></p>
<h1 id="上线实践"><a href="#上线实践" class="headerlink" title="上线实践"></a>上线实践</h1><h2 id="使用-winston-记录日记"><a href="#使用-winston-记录日记" class="headerlink" title="使用 winston 记录日记"></a>使用 winston 记录日记</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> winston = <span class="built_in">require</span>(<span class="string">&#x27;winston&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> moment = <span class="built_in">require</span>(<span class="string">&#x27;moment&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> logger = <span class="keyword">new</span> (winston.<span class="property">Logger</span>)(&#123;</span><br><span class="line">  <span class="attr">transports</span>: [</span><br><span class="line">    <span class="keyword">new</span> (winston.<span class="property">transports</span>.<span class="property">Console</span>)(&#123;</span><br><span class="line">      <span class="attr">timestamp</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">moment</span>().<span class="title function_">format</span>(<span class="string">&#x27;YYYY-MM-DD HH:mm:ss&#x27;</span>)</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">formatter</span>: <span class="keyword">function</span>(<span class="params">params</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> time = params.<span class="title function_">timestamp</span>() <span class="comment">// 时间</span></span><br><span class="line">        <span class="keyword">let</span> message = params.<span class="property">message</span> <span class="comment">// 手动信息</span></span><br><span class="line">        <span class="keyword">let</span> meta = params.<span class="property">meta</span> &amp;&amp; <span class="title class_">Object</span>.<span class="title function_">keys</span>(params.<span class="property">meta</span>).<span class="property">length</span> ? <span class="string">&#x27;\n\t&#x27;</span>+ <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(params.<span class="property">meta</span>) : <span class="string">&#x27;&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;time&#125;</span> <span class="subst">$&#123;message&#125;</span>`</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> (winston.<span class="property">transports</span>.<span class="property">File</span>)(&#123;</span><br><span class="line">      <span class="attr">filename</span>: <span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/../winston/winston.log`</span>,</span><br><span class="line">      <span class="attr">json</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">timestamp</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">moment</span>().<span class="title function_">format</span>(<span class="string">&#x27;YYYY-MM-DD HH:mm:ss&#x27;</span>)</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">formatter</span>: <span class="keyword">function</span>(<span class="params">params</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> time = params.<span class="title function_">timestamp</span>() <span class="comment">// 时间</span></span><br><span class="line">        <span class="keyword">let</span> message = params.<span class="property">message</span> <span class="comment">// 手动信息</span></span><br><span class="line">        <span class="keyword">let</span> meta = params.<span class="property">meta</span> &amp;&amp; <span class="title class_">Object</span>.<span class="title function_">keys</span>(params.<span class="property">meta</span>).<span class="property">length</span> ? <span class="string">&#x27;\n\t&#x27;</span>+ <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(params.<span class="property">meta</span>) : <span class="string">&#x27;&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;time&#125;</span> <span class="subst">$&#123;message&#125;</span>`</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = logger</span><br><span class="line"></span><br><span class="line"><span class="comment">// logger.error(&#x27;error&#x27;)</span></span><br><span class="line"><span class="comment">// logger.warm(&#x27;warm&#x27;)</span></span><br><span class="line"><span class="comment">// logger.info(&#x27;info&#x27;)</span></span><br></pre></td></tr></table></figure>

<h2 id="委托反向代理"><a href="#委托反向代理" class="headerlink" title="委托反向代理"></a>委托反向代理</h2><p>Node 处理 CPU 密集型任务，如 gzipping，SSL termination 等，表现糟糕。相反，使用一个真正的中间件服务像 Nginx 更好。否则可怜的单线程 Node 将不幸地忙于处理网络任务，而不是处理应用程序核心，性能会相应降低。</p>
<p>虽然 express.js 通过一些 connect 中间件处理静态文件，但你不应该使用它。Nginx 可以更好地处理静态文件，并可以防止请求动态内容堵塞我们的 node 进程。</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置 gzip 压缩</span></span><br><span class="line">gzip on;</span><br><span class="line">gzip_comp_level 6;</span><br><span class="line">gzip_vary on;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置 upstream</span></span><br><span class="line">upstream myApplication &#123;</span><br><span class="line">  server 127.0.0.1:3000;</span><br><span class="line">  server 127.0.0.1:3001;</span><br><span class="line">  keepalive 64;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">#定义 web server</span></span><br><span class="line">server &#123;</span><br><span class="line">  <span class="comment"># configure server with ssl and error pages</span></span><br><span class="line">  listen 80;</span><br><span class="line">  listen 443 ssl;</span><br><span class="line">  ssl_certificate /some/location/sillyfacesociety.com.bundle.crt;</span><br><span class="line">  error_page 502 /errors/502.html;</span><br><span class="line"></span><br><span class="line">  <span class="comment"># handling static content</span></span><br><span class="line">  location ~ ^/(images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) &#123;</span><br><span class="line">  root /usr/local/silly_face_society/node/public;</span><br><span class="line">  access_log off;</span><br><span class="line">  expires max;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="检测有漏洞的依赖项"><a href="#检测有漏洞的依赖项" class="headerlink" title="检测有漏洞的依赖项"></a>检测有漏洞的依赖项</h2><p><a target="_blank" rel="noopener" href="https://docs.npmjs.com/cli/audit">https://docs.npmjs.com/cli/audit</a></p>
<h2 id="PM2-HTTP-集群配置"><a href="#PM2-HTTP-集群配置" class="headerlink" title="PM2 HTTP 集群配置"></a>PM2 HTTP 集群配置</h2><h3 id="工作线程配置"><a href="#工作线程配置" class="headerlink" title="工作线程配置"></a>工作线程配置</h3><ul>
<li><code>pm2 start app.js -i 4</code>，<code>-i 4</code> 是以 cluster_mode 形式运行 app，有 4 个工作线程，如果配置 <code>0</code>，PM2 会根据 CPU 核心数来生成对应的工作线程</li>
<li>工作线程挂了 PM2 会立即将其重启</li>
<li><code>pm2 scale &lt;app name&gt; &lt;n&gt;</code> 对集群进行扩展</li>
</ul>
<h3 id="PM2-自动启动"><a href="#PM2-自动启动" class="headerlink" title="PM2 自动启动"></a>PM2 自动启动</h3><ul>
<li><code>pm2 save</code> 保存当前运行的应用</li>
<li><code>pm2 startup</code> 启动</li>
</ul>
<h1 id="性能实践"><a href="#性能实践" class="headerlink" title="性能实践"></a>性能实践</h1><h2 id="避免使用-Lodash"><a href="#避免使用-Lodash" class="headerlink" title="避免使用 Lodash"></a>避免使用 Lodash</h2><ul>
<li>使用像 lodash 这样的方法库这会导致不必要的依赖和较慢的性能</li>
<li>随着新的 V8 引擎和新的 ES 标准的引入，原生方法得到了改进，现在性能比方法库提高了 50%</li>
</ul>
<p>使用 ESLint 插件检测：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;extends&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;plugin:you-dont-need-lodash-underscore/compatible&quot;</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="benchmark"><a href="#benchmark" class="headerlink" title="benchmark"></a>benchmark</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> _ = <span class="built_in">require</span>(<span class="string">&#x27;lodash&#x27;</span>),</span><br><span class="line">  __ = <span class="built_in">require</span>(<span class="string">&#x27;underscore&#x27;</span>),</span><br><span class="line">  <span class="title class_">Suite</span> = <span class="built_in">require</span>(<span class="string">&#x27;benchmark&#x27;</span>).<span class="property">Suite</span>,</span><br><span class="line">  opts = <span class="built_in">require</span>(<span class="string">&#x27;./utils&#x27;</span>);</span><br><span class="line">  <span class="comment">//cf. https://github.com/Berkmann18/NativeVsUtils/blob/master/utils.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> concatSuite = <span class="keyword">new</span> <span class="title class_">Suite</span>(<span class="string">&#x27;concat&#x27;</span>, opts);</span><br><span class="line"><span class="keyword">const</span> array = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"></span><br><span class="line">concatSuite.<span class="title function_">add</span>(<span class="string">&#x27;lodash&#x27;</span>, <span class="function">() =&gt;</span> _.<span class="title function_">concat</span>(array, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>))</span><br><span class="line">  .<span class="title function_">add</span>(<span class="string">&#x27;underscore&#x27;</span>, <span class="function">() =&gt;</span> __.<span class="title function_">concat</span>(array, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>))</span><br><span class="line">  .<span class="title function_">add</span>(<span class="string">&#x27;native&#x27;</span>, <span class="function">() =&gt;</span> array.<span class="title function_">concat</span>(<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>))</span><br><span class="line">  .<span class="title function_">run</span>(&#123; <span class="string">&#x27;async&#x27;</span>: <span class="literal">true</span> &#125;);</span><br></pre></td></tr></table></figure>

<h2 id="使用-prof-进行性能分析"><a href="#使用-prof-进行性能分析" class="headerlink" title="使用 prof 进行性能分析"></a>使用 prof 进行性能分析</h2><ul>
<li>使用 tick-processor 工具处理分析</li>
</ul>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node --prof profile-test.js</span><br></pre></td></tr></table></figure>

<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install tick -g</span><br><span class="line"></span><br><span class="line">node-tick-processor</span><br></pre></td></tr></table></figure>

<h2 id="使用-headdump-堆快照"><a href="#使用-headdump-堆快照" class="headerlink" title="使用 headdump 堆快照"></a>使用 headdump 堆快照</h2><ul>
<li>代码加载模块进行快照文件生成</li>
<li>Chrome Profiles 加载快照文件</li>
</ul>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add heapdump -D</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> heapdump = <span class="built_in">require</span>(<span class="string">&#x27;heapdump&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> string = <span class="string">&#x27;1 string to rule them all&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> leakyArr = [];</span><br><span class="line"><span class="keyword">let</span> count = <span class="number">2</span>;</span><br><span class="line"><span class="built_in">setInterval</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  leakyArr.<span class="title function_">push</span>(string.<span class="title function_">replace</span>(<span class="regexp">/1/g</span>, count++));</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (heapdump.<span class="title function_">writeSnapshot</span>()) <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;wrote snapshot&#x27;</span>);</span><br><span class="line">&#125;, <span class="number">20000</span>);</span><br></pre></td></tr></table></figure>

<h1 id="应用安全清单"><a href="#应用安全清单" class="headerlink" title="应用安全清单"></a>应用安全清单</h1><h2 id="helmet-设置安全响应头"><a href="#helmet-设置安全响应头" class="headerlink" title="helmet 设置安全响应头"></a>helmet 设置安全响应头</h2><p>检测头部配置：<a target="_blank" rel="noopener" href="https://securityheaders.com/">Security Headers</a>。</p>
<p>应用程序应该使用安全的 header 来防止攻击者使用常见的攻击方式，诸如跨站点脚本攻击(XSS)、跨站请求伪造(CSRF)。可以使用模块 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/helmet">helmet</a> 轻松进行配置。</p>
<ul>
<li>构造<ul>
<li>X-Frame-Options：sameorigin。提供点击劫持保护，iframe 只能同源。</li>
</ul>
</li>
<li>传输<ul>
<li>Strict-Transport-Security：max-age&#x3D;31536000; includeSubDomains。强制 HTTPS，这减少了web 应用程序中错误通过 cookies 和外部链接，泄露会话数据，并防止中间人攻击</li>
</ul>
</li>
<li>内容<ul>
<li>X-Content-Type-Options：nosniff。阻止从声明的内容类型中嗅探响应，减少了用户上传恶意内容造成的风险</li>
<li>Content-Type：text&#x2F;html;charset&#x3D;utf-8。指示浏览器将页面解释为特定的内容类型，而不是依赖浏览器进行假设</li>
</ul>
</li>
<li>XSS<ul>
<li>X-XSS-Protection：1; mode&#x3D;block。启用了内置于最新 web 浏览器中的跨站点脚本(XSS)过滤器</li>
</ul>
</li>
<li>下载<ul>
<li>X-Download-Options：noopen。</li>
</ul>
</li>
<li>缓存<ul>
<li>Cache-Control：no-cache。web 应中返回的数据可以由用户浏览器以及中间代理缓存。该指令指示他们不要保留页面内容，以免其他人从这些缓存中访问敏感内容</li>
<li>Pragma：no-cache。同上</li>
<li>Expires：-1。web 响应中返回的数据可以由用户浏览器以及中间代理缓存。该指令通过将到期时间设置为一个值来防止这种情况。</li>
</ul>
</li>
<li>访问控制<ul>
<li>Access-Control-Allow-Origin：not *。’Access-Control-Allow-Origin: *’ 默认在现代浏览器中禁用</li>
<li>X-Permitted-Cross-Domain-Policies：master-only。指示只有指定的文件在此域中才被视为有效</li>
</ul>
</li>
<li>内容安全策略<ul>
<li>Content-Security-Policy：内容安全策略需要仔细调整并精确定义策略</li>
</ul>
</li>
<li>服务器信息<ul>
<li>Server：不显示。</li>
</ul>
</li>
</ul>
<h2 id="使用-security-linter-插件"><a href="#使用-security-linter-插件" class="headerlink" title="使用 security-linter 插件"></a>使用 security-linter 插件</h2><p>使用安全检验插件 <a target="_blank" rel="noopener" href="https://github.com/nodesecurity/eslint-plugin-security">eslint-plugin-security</a> 或者 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/tslint-config-security">tslint-config-security</a>。</p>
<h2 id="koa-ratelimit-限制并发请求"><a href="#koa-ratelimit-限制并发请求" class="headerlink" title="koa-ratelimit 限制并发请求"></a>koa-ratelimit 限制并发请求</h2><p>DOS 攻击非常流行而且相对容易处理。使用外部服务，比如 cloud 负载均衡, cloud 防火墙, nginx, 或者（对于小的，不是那么重要的app）一个速率限制中间件(比如 <a target="_blank" rel="noopener" href="https://github.com/koajs/ratelimit">koa-ratelimit</a>)，来实现速率限制。</p>
<h2 id="纯文本机密信息放置"><a href="#纯文本机密信息放置" class="headerlink" title="纯文本机密信息放置"></a>纯文本机密信息放置</h2><p>存储在源代码管理中的机密信息必须进行加密和管理 (滚动密钥(rolling keys)、过期时间、审核等)。使用 pre-commit&#x2F;push 钩子防止意外提交机密信息。</p>
<h2 id="ORM-ODM-库防止查询注入漏洞"><a href="#ORM-ODM-库防止查询注入漏洞" class="headerlink" title="ORM&#x2F;ODM 库防止查询注入漏洞"></a>ORM&#x2F;ODM 库防止查询注入漏洞</h2><p>要防止 SQL&#x2F;NoSQL 注入和其他恶意攻击, 请始终使用 ORM&#x2F;ODM 或 database 库来转义数据或支持命名的或索引的参数化查询, 并注意验证用户输入的预期类型。不要只使用 JavaScript 模板字符串或字符串串联将值插入到查询语句中, 因为这会将应用程序置于广泛的漏洞中。</p>
<p>库：</p>
<ul>
<li>TypeORM</li>
<li>sequelize</li>
<li>mongoose</li>
<li>Knex</li>
<li>Objection.js</li>
<li>waterline</li>
</ul>
<h2 id="使用-Bcrypt-代替-Crypto"><a href="#使用-Bcrypt-代替-Crypto" class="headerlink" title="使用 Bcrypt 代替 Crypto"></a>使用 Bcrypt 代替 Crypto</h2><p>密码或机密信息(API 密钥)应该使用安全的 hash + salt 函数(<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/bcrypt">bcrypt</a>)来存储, 因为性能和安全原因, 这应该是其 JavaScript 实现的首选。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用10个哈希回合异步生成安全密码</span></span><br><span class="line">bcrypt.<span class="title function_">hash</span>(<span class="string">&#x27;myPassword&#x27;</span>, <span class="number">10</span>, <span class="keyword">function</span>(<span class="params">err, hash</span>) &#123;</span><br><span class="line">  <span class="comment">// 在用户记录中存储安全哈希</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将提供的密码输入与已保存的哈希进行比较</span></span><br><span class="line">bcrypt.<span class="title function_">compare</span>(<span class="string">&#x27;somePassword&#x27;</span>, hash, <span class="keyword">function</span>(<span class="params">err, match</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span>(match) &#123;</span><br><span class="line">   <span class="comment">// 密码匹配</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">   <span class="comment">// 密码不匹配</span></span><br><span class="line">  &#125; </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="转义-HTML、JS-和-CSS-输出"><a href="#转义-HTML、JS-和-CSS-输出" class="headerlink" title="转义 HTML、JS 和 CSS 输出"></a>转义 HTML、JS 和 CSS 输出</h2><p>发送给浏览器的不受信任数据可能会被执行, 而不是显示, 这通常被称为跨站点脚本(XSS)攻击。使用专用库将数据显式标记为不应执行的纯文本内容(例如:编码、转义)，可以减轻这种问题。</p>
<h2 id="验证传入的-JSON-schemas"><a href="#验证传入的-JSON-schemas" class="headerlink" title="验证传入的 JSON schemas"></a>验证传入的 JSON schemas</h2><p>验证传入请求的 body payload，并确保其符合预期要求, 如果没有, 则快速报错。为了避免每个路由中繁琐的验证编码, 您可以使用基于 JSON 的轻量级验证架构，比如 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/jsonschema">jsonschema</a> 或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/joi">joi</a></p>
<h2 id="支持黑名单的-JWT"><a href="#支持黑名单的-JWT" class="headerlink" title="支持黑名单的 JWT"></a>支持黑名单的 JWT</h2><p>当使用 JSON Web Tokens(例如, 通过 <a target="_blank" rel="noopener" href="https://github.com/jaredhanson/passport">Passport.js</a>), 默认情况下, 没有任何机制可以从发出的令牌中撤消访问权限。一旦发现了一些恶意用户活动, 只要它们持有有效的标记, 就无法阻止他们访问系统。通过实现一个不受信任令牌的黑名单，并在每个请求上验证，来减轻此问题。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> jwt = <span class="built_in">require</span>(<span class="string">&#x27;express-jwt&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> blacklist = <span class="built_in">require</span>(<span class="string">&#x27;express-jwt-blacklist&#x27;</span>);</span><br><span class="line"> </span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">jwt</span>(&#123;</span><br><span class="line">  <span class="attr">secret</span>: <span class="string">&#x27;my-secret&#x27;</span>,</span><br><span class="line">  <span class="attr">isRevoked</span>: blacklist.<span class="property">isRevoked</span></span><br><span class="line">&#125;));</span><br><span class="line"> </span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/logout&#x27;</span>, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">  blacklist.<span class="title function_">revoke</span>(req.<span class="property">user</span>)</span><br><span class="line">  res.<span class="title function_">sendStatus</span>(<span class="number">200</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="限制每个用户允许的登录请求"><a href="#限制每个用户允许的登录请求" class="headerlink" title="限制每个用户允许的登录请求"></a>限制每个用户允许的登录请求</h2><p>一类保护暴力破解的中间件，比如 express-brute，应该被用在 express 的应用中，来防止暴力&#x2F;字典攻击；这类攻击主要应用于一些敏感路由，比如 <code>/admin</code> 或者 <code>/login</code>，基于某些请求属性, 如用户名, 或其他标识符, 如正文参数等。否则攻击者可以发出无限制的密码匹配尝试, 以获取对应用程序中特权帐户的访问权限。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">ExpressBrute</span> = <span class="built_in">require</span>(<span class="string">&#x27;express-brute&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">RedisStore</span> = <span class="built_in">require</span>(<span class="string">&#x27;express-brute-redis&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> redisStore = <span class="keyword">new</span> <span class="title class_">RedisStore</span>(&#123;</span><br><span class="line">  <span class="attr">host</span>: <span class="string">&#x27;127.0.0.1&#x27;</span>,</span><br><span class="line">  <span class="attr">port</span>: <span class="number">6379</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Start slowing requests after 5 failed </span></span><br><span class="line"><span class="comment">// attempts to login for the same user</span></span><br><span class="line"><span class="keyword">const</span> loginBruteforce = <span class="keyword">new</span> <span class="title class_">ExpressBrute</span>(redisStore, &#123;</span><br><span class="line">  <span class="attr">freeRetries</span>: <span class="number">5</span>,</span><br><span class="line">  <span class="attr">minWait</span>: <span class="number">5</span> * <span class="number">60</span> * <span class="number">1000</span>, <span class="comment">// 5 minutes</span></span><br><span class="line">  <span class="attr">maxWait</span>: <span class="number">60</span> * <span class="number">60</span> * <span class="number">1000</span>, <span class="comment">// 1 hour</span></span><br><span class="line">  <span class="attr">failCallback</span>: failCallback,</span><br><span class="line">  <span class="attr">handleStoreError</span>: handleStoreErrorCallback</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/login&#x27;</span>,</span><br><span class="line">  loginBruteforce.<span class="title function_">getMiddleware</span>(&#123;</span><br><span class="line">    <span class="attr">key</span>: <span class="keyword">function</span> (<span class="params">req, res, next</span>) &#123;</span><br><span class="line">      <span class="comment">// prevent too many attempts for the same username</span></span><br><span class="line">      <span class="title function_">next</span>(req.<span class="property">body</span>.<span class="property">username</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;), <span class="comment">// error 403 if we hit this route too often</span></span><br><span class="line">  <span class="keyword">function</span> (<span class="params">req, res, next</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">User</span>.<span class="title function_">isValidLogin</span>(req.<span class="property">body</span>.<span class="property">username</span>, req.<span class="property">body</span>.<span class="property">password</span>)) &#123;</span><br><span class="line">      <span class="comment">// reset the failure counter for valid login</span></span><br><span class="line">      req.<span class="property">brute</span>.<span class="title function_">reset</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        res.<span class="title function_">redirect</span>(<span class="string">&#x27;/&#x27;</span>); <span class="comment">// logged in</span></span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// handle invalid user</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<h2 id="使用非-root-用户运行-Node-js"><a href="#使用非-root-用户运行-Node-js" class="headerlink" title="使用非 root 用户运行 Node.js"></a>使用非 root 用户运行 Node.js</h2><p>Node.js 作为一个具有无限权限的 root 用户运行，这是一种普遍的情景。例如，在 Docker 容器中，这是默认行为。建议创建一个非 root 用户，并保存到 Docker 镜像中（下面给出了示例），或者通过调用带有”-u username” 的容器来代表此用户运行该进程。否则在服务器上运行脚本的攻击者在本地计算机上获得无限制的权利 (例如，改变 iptable，引流到他的服务器上)</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:latest</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package.json .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm install</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">3000</span></span><br><span class="line"><span class="keyword">USER</span> node</span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;node&quot;</span>, <span class="string">&quot;server.js&quot;</span>]</span></span><br></pre></td></tr></table></figure>

<h2 id="使用反向代理或中间件限制负载大小"><a href="#使用反向代理或中间件限制负载大小" class="headerlink" title="使用反向代理或中间件限制负载大小"></a>使用反向代理或中间件限制负载大小</h2><p>请求 body 有效载荷越大, Node.js 的单线程就越难处理它。这是攻击者在没有大量请求(DOS&#x2F;DDOS 攻击)的情况下，就可以让服务器跪下的机会。在边缘上（例如，防火墙，ELB）限制传入请求的 body 大小，或者通过配置 <code>express body parser</code> 仅接收小的载荷，可以减轻这种问题。否则您的应用程序将不得不处理大的请求, 无法处理它必须完成的其他重要工作, 从而导致对 DOS 攻击的性能影响和脆弱性。</p>
<p>express：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// body-parser defaults to a body size limit of 300kb</span></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">json</span>(&#123; <span class="attr">limit</span>: <span class="string">&#x27;300kb&#x27;</span> &#125;)); </span><br><span class="line"></span><br><span class="line"><span class="comment">// Request with json body</span></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/json&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Check if request payload content-type matches json</span></span><br><span class="line">    <span class="comment">// because body-parser does not check for content types</span></span><br><span class="line">    <span class="keyword">if</span> (!req.<span class="title function_">is</span>(<span class="string">&#x27;json&#x27;</span>)) &#123;</span><br><span class="line">        <span class="keyword">return</span> res.<span class="title function_">sendStatus</span>(<span class="number">415</span>); <span class="comment">// Unsupported media type if request doesn&#x27;t have JSON body</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    res.<span class="title function_">send</span>(<span class="string">&#x27;Hooray, it worked!&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Example app listening on port 3000!&#x27;</span>));</span><br></pre></td></tr></table></figure>

<p>nginx：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">http &#123;</span><br><span class="line">    ...</span><br><span class="line">    # Limit the body size for ALL incoming requests to 1 MB</span><br><span class="line">    client_max_body_size 1m;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    ...</span><br><span class="line">    # Limit the body size for incoming requests to this specific server block to 1 MB</span><br><span class="line">    client_max_body_size 1m;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">location /upload &#123;</span><br><span class="line">    ...</span><br><span class="line">    # Limit the body size for incoming requests to this route to 1 MB</span><br><span class="line">    client_max_body_size 1m;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="防止-RegEx-让-NodeJS-过载"><a href="#防止-RegEx-让-NodeJS-过载" class="headerlink" title="防止 RegEx 让 NodeJS 过载"></a>防止 RegEx 让 NodeJS 过载</h2><p>匹配文本的用户输入需要大量的 CPU 周期来处理。在某种程度上，正则处理是效率低下的，比如验证 10 个单词的单个请求可能阻止整个 event loop 长达6秒。由于这个原因，偏向第三方的验证包，比如<a target="_blank" rel="noopener" href="https://github.com/chriso/validator.js">validator.js</a>，而不是采用正则，或者使用 <a target="_blank" rel="noopener" href="https://github.com/substack/safe-regex">safe-regex</a> 来检测有问题的正则表达式。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> saferegex = <span class="built_in">require</span>(<span class="string">&#x27;safe-regex&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> emailRegex = <span class="regexp">/^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@)&#123;1&#125;[a-z0-9]+[.]&#123;1&#125;(([a-z]&#123;2,3&#125;)|([a-z]&#123;2,3&#125;[.]&#123;1&#125;[a-z]&#123;2,3&#125;))$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// should output false because the emailRegex is vulnerable to redos attacks</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">saferegex</span>(emailRegex));</span><br><span class="line"></span><br><span class="line"><span class="comment">// instead of the regex pattern, use validator:</span></span><br><span class="line"><span class="keyword">const</span> validator = <span class="built_in">require</span>(<span class="string">&#x27;validator&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(validator.<span class="title function_">isEmail</span>(<span class="string">&#x27;liran.tal@gmail.com&#x27;</span>));</span><br></pre></td></tr></table></figure>

<h2 id="在沙箱中运行不安全代码"><a href="#在沙箱中运行不安全代码" class="headerlink" title="在沙箱中运行不安全代码"></a>在沙箱中运行不安全代码</h2><p>当任务执行在运行时给出的外部代码时(例如, 插件), 使用任何类型的沙盒执行环境保护主代码，并隔离开主代码和插件。这可以通过一个专用的过程来实现 (例如:cluster.fork()), 无服务器环境或充当沙盒的专用 npm 包。</p>
<ul>
<li>一个专门的子进程 - 这提供了一个快速的信息隔离, 但要求制约子进程, 限制其执行时间, 并从错误中恢复</li>
<li>一个基于云的无服务框架满足所有沙盒要求，但动态部署和调用Faas方法不是本部分的内容</li>
<li>一些 npm 库，比如 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/sandbox">sandbox</a> 和 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/vm2">vm2</a> 允许通过一行代码执行隔离代码。尽管后一种选择在简单中获胜, 但它提供了有限的保护。</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Sandbox</span> = <span class="built_in">require</span>(<span class="string">&quot;sandbox&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> s = <span class="keyword">new</span> <span class="title class_">Sandbox</span>();</span><br><span class="line"></span><br><span class="line">s.<span class="title function_">run</span>( <span class="string">&quot;lol)hai&quot;</span>, <span class="keyword">function</span>(<span class="params"> output </span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(output);</span><br><span class="line">  <span class="comment">//output=&#x27;Synatx error&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Example 4 - Restricted code</span></span><br><span class="line">s.<span class="title function_">run</span>( <span class="string">&quot;process.platform&quot;</span>, <span class="keyword">function</span>(<span class="params"> output </span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(output);</span><br><span class="line">  <span class="comment">//output=Null</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Example 5 - Infinite loop</span></span><br><span class="line">s.<span class="title function_">run</span>( <span class="string">&quot;while (true) &#123;&#125;&quot;</span>, <span class="keyword">function</span>(<span class="params"> output </span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(output);</span><br><span class="line">  <span class="comment">//output=&#x27;Timeout&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="隐藏客户端的错误详细信息"><a href="#隐藏客户端的错误详细信息" class="headerlink" title="隐藏客户端的错误详细信息"></a>隐藏客户端的错误详细信息</h2><p>默认情况下, 集成的 express 错误处理程序隐藏错误详细信息。但是, 极有可能, 您实现自己的错误处理逻辑与自定义错误对象(被许多人认为是最佳做法)。如果这样做, 请确保不将整个 Error 对象返回到客户端, 这可能包含一些敏感的应用程序详细信息。否则敏感应用程序详细信息(如服务器文件路径、使用中的第三方模块和可能被攻击者利用的应用程序的其他内部工作流)可能会从 stack trace 发现的信息中泄露。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// production error handler</span><br><span class="line">// no stacktraces leaked to user</span><br><span class="line">app.use(function(err, req, res, next) &#123;</span><br><span class="line">    res.status(err.status || 500);</span><br><span class="line">    res.render(&#x27;error&#x27;, &#123;</span><br><span class="line">        message: err.message,</span><br><span class="line">        error: &#123;&#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="对-npm-或-Yarn，配置-2FA"><a href="#对-npm-或-Yarn，配置-2FA" class="headerlink" title="对 npm 或 Yarn，配置 2FA"></a>对 npm 或 Yarn，配置 2FA</h2><p>开发链中的任何步骤都应使用 MFA(多重身份验证)进行保护, npm&#x2F;Yarn 对于那些能够掌握某些开发人员密码的攻击者来说是一个很好的机会。使用开发人员凭据, 攻击者可以向跨项目和服务广泛安装的库中注入恶意代码。甚至可能在网络上公开发布。在 npm 中启用两层身份验证（2-factor-authentication）, 攻击者几乎没有机会改变您的软件包代码。</p>
<p><a target="_blank" rel="noopener" href="https://itnext.io/eslint-backdoor-what-it-is-and-how-to-fix-the-issue-221f58f1a8c8">https://itnext.io/eslint-backdoor-what-it-is-and-how-to-fix-the-issue-221f58f1a8c8</a></p>
<h2 id="session-中间件设置"><a href="#session-中间件设置" class="headerlink" title="session 中间件设置"></a>session 中间件设置</h2><p>每个 web 框架和技术都有其已知的弱点，告诉攻击者我们使用的 web 框架对他们来说是很大的帮助。使用 session 中间件的默认设置, 可以以类似于 <code>X-Powered-Byheader</code> 的方式向模块和框架特定的劫持攻击公开您的应用。尝试隐藏识别和揭露技术栈的任何内容(例如:Nonde.js, express)。否则可以通过不安全的连接发送cookie, 攻击者可能会使用会话标识来标识web应用程序的基础框架以及特定于模块的漏洞。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// using the express session middleware</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">session</span>(&#123;  </span><br><span class="line"> <span class="attr">secret</span>: <span class="string">&#x27;youruniquesecret&#x27;</span>, <span class="comment">// secret string used in the signing of the session ID that is stored in the cookie</span></span><br><span class="line"> <span class="attr">name</span>: <span class="string">&#x27;youruniquename&#x27;</span>, <span class="comment">// set a unique name to remove the default connect.sid</span></span><br><span class="line"> <span class="attr">cookie</span>: &#123;</span><br><span class="line">   <span class="attr">httpOnly</span>: <span class="literal">true</span>, <span class="comment">// minimize risk of XSS attacks by restricting the client from reading the cookie</span></span><br><span class="line">   <span class="attr">secure</span>: <span class="literal">true</span>, <span class="comment">// only send cookie over https</span></span><br><span class="line">   <span class="attr">maxAge</span>: <span class="number">60000</span>*<span class="number">60</span>*<span class="number">24</span> <span class="comment">// set cookie expiry length in ms</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>

<h2 id="csurf-防止-CSRF"><a href="#csurf-防止-CSRF" class="headerlink" title="csurf 防止 CSRF"></a>csurf 防止 CSRF</h2><p>路由层：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cookieParser = <span class="built_in">require</span>(<span class="string">&#x27;cookie-parser&#x27;</span>);  </span><br><span class="line"><span class="keyword">var</span> csrf = <span class="built_in">require</span>(<span class="string">&#x27;csurf&#x27;</span>);  </span><br><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">&#x27;body-parser&#x27;</span>);  </span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置路由中间件</span></span><br><span class="line"><span class="keyword">var</span> csrfProtection = <span class="title function_">csrf</span>(&#123; <span class="attr">cookie</span>: <span class="literal">true</span> &#125;);  </span><br><span class="line"><span class="keyword">var</span> parseForm = bodyParser.<span class="title function_">urlencoded</span>(&#123; <span class="attr">extended</span>: <span class="literal">false</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 我们需要这个，因为在 csrfProtection 中 “cookie” 是正确的</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">cookieParser</span>());</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/form&#x27;</span>, csrfProtection, <span class="keyword">function</span>(<span class="params">req, res</span>) &#123;  </span><br><span class="line">  <span class="comment">// 将 CSRFToken 传递给视图</span></span><br><span class="line">  res.<span class="title function_">render</span>(<span class="string">&#x27;send&#x27;</span>, &#123; <span class="attr">csrfToken</span>: req.<span class="title function_">csrfToken</span>() &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/process&#x27;</span>, parseForm, csrfProtection, <span class="keyword">function</span>(<span class="params">req, res</span>) &#123;  </span><br><span class="line">  res.<span class="title function_">send</span>(<span class="string">&#x27;data is being processed&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>展示层：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">&quot;/process&quot;</span> <span class="attr">method</span>=<span class="string">&quot;POST&quot;</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;hidden&quot;</span> <span class="attr">name</span>=<span class="string">&quot;_csrf&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&#123;&#123;csrfToken&#125;&#125;&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  Favorite color: <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;favoriteColor&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span>&gt;</span>Submit<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span>  </span><br></pre></td></tr></table></figure>

<h1 id="综合应用"><a href="#综合应用" class="headerlink" title="综合应用"></a>综合应用</h1><h2 id="watch-服务"><a href="#watch-服务" class="headerlink" title="watch 服务"></a>watch 服务</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> exec = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">exec</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">watch</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> child = <span class="title function_">exec</span>(<span class="string">&#x27;node server.js&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> watcher = fs.<span class="title function_">watch</span>(__dirname + <span class="string">&#x27;/server.js&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;File changed, reloading.&#x27;</span>);</span><br><span class="line">    child.<span class="title function_">kill</span>();</span><br><span class="line">    watcher.<span class="title function_">close</span>();</span><br><span class="line">    <span class="title function_">watch</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">watch</span>();</span><br></pre></td></tr></table></figure>

<h2 id="RESTful-web-应用"><a href="#RESTful-web-应用" class="headerlink" title="RESTful web 应用"></a>RESTful web 应用</h2><ul>
<li><p>REST 意思是表征性状态传输</p>
</li>
<li><p>使用正确的 HTTP 方法、URLs 和头部信息来创建语义化 RESTful API</p>
</li>
<li><p>GET &#x2F;gages：获取</p>
</li>
<li><p>POST &#x2F;pages：创建</p>
</li>
<li><p>GET &#x2F;pages&#x2F;10：获取 pages10</p>
</li>
<li><p>PATCH &#x2F;pages&#x2F;10：更新 pages10</p>
</li>
<li><p>PUT &#x2F;pages&#x2F;10：替换 pages10</p>
</li>
<li><p>DELETE &#x2F;pages&#x2F;10：删除 pages10</p>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> app;</span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> routes = <span class="built_in">require</span>(<span class="string">&#x27;./routes&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">json</span>()); <span class="comment">// 使用 JSON body 解析</span></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">methodOverride</span>()); <span class="comment">// 允许一个查询参数来制定额外的 HTTP 方法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 资源使用的路由</span></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/pages&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">index</span>);</span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/pages/:id&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">show</span>);</span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/pages&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">create</span>);</span><br><span class="line">app.<span class="title function_">patch</span>(<span class="string">&#x27;/pages/:id&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">patch</span>);</span><br><span class="line">app.<span class="title function_">put</span>(<span class="string">&#x27;/pages/:id&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">update</span>);</span><br><span class="line">app.<span class="title function_">del</span>(<span class="string">&#x27;/pages/:id&#x27;</span>, routes.<span class="property">pages</span>.<span class="property">remove</span>);</span><br></pre></td></tr></table></figure>

<h2 id="中间件应用"><a href="#中间件应用" class="headerlink" title="中间件应用"></a>中间件应用</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Schema</span> = <span class="built_in">require</span>(<span class="string">&#x27;validate&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> xml2json = <span class="built_in">require</span>(<span class="string">&#x27;xml2json&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Page</span> = <span class="keyword">new</span> <span class="title class_">Schema</span>();</span><br><span class="line"></span><br><span class="line"><span class="title class_">Page</span>.<span class="title function_">path</span>(<span class="string">&#x27;title&#x27;</span>).<span class="title function_">type</span>(<span class="string">&#x27;string&#x27;</span>).required(); <span class="comment">// 数据校验确保页面有标题</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ValidatorError</span>(<span class="params">errors</span>) &#123; <span class="comment">// 从错误对象继承，校验出现的错误在错误中间件处理</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">statusCode</span> = <span class="number">400</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">message</span> = errors.<span class="title function_">join</span>(<span class="string">&#x27;, &#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line">util.<span class="title function_">inherits</span>(<span class="title class_">ValidatorError</span>, <span class="title class_">Error</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">xmlMiddleware</span>(<span class="params">req, res, next</span>) &#123; <span class="comment">// 处理 xml 的中间件</span></span><br><span class="line">  <span class="keyword">if</span> (!req.<span class="title function_">is</span>(<span class="string">&#x27;xml&#x27;</span>)) <span class="keyword">return</span> <span class="title function_">next</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> body = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  req.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">str</span>) &#123; <span class="comment">// 从客户端读到数据时触发</span></span><br><span class="line">    body += str;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  req.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    req.<span class="property">body</span> = xml2json.<span class="title function_">toJson</span>(body.<span class="title function_">toString</span>(), &#123;</span><br><span class="line">      <span class="attr">object</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">sanitize</span>: <span class="literal">false</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkValidXml</span>(<span class="params">req, res, next</span>) &#123; <span class="comment">// 数据校验中间件</span></span><br><span class="line">  <span class="keyword">const</span> page = <span class="title class_">Page</span>.<span class="title function_">validate</span>(req.<span class="property">body</span>.<span class="property">page</span>);</span><br><span class="line">  <span class="keyword">if</span> (page.<span class="property">errors</span>.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="title function_">next</span>(<span class="keyword">new</span> <span class="title class_">ValidatorError</span>(page.<span class="property">errors</span>)); <span class="comment">// 传递错误给 next 阻止路由继续运行</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">errorHandler</span>(<span class="params">err, req, res, next</span>) &#123; <span class="comment">// 错误处理中间件</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;errorHandler&#x27;</span>, err);</span><br><span class="line">  res.<span class="title function_">send</span>(err.<span class="property">statusCode</span> || <span class="number">500</span>, err.<span class="property">message</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(xmlMiddleware); <span class="comment">// 应用 XML 中间件到所有的请求中</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/pages&#x27;</span>, checkValidXml, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123; <span class="comment">// 特定的请求校验 xml</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Valid page:&#x27;</span>, req.<span class="property">body</span>.<span class="property">page</span>);</span><br><span class="line">  res.<span class="title function_">send</span>(req.<span class="property">body</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(errorHandler); <span class="comment">// 添加错误处理中间件</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<h2 id="通过事件组织应用"><a href="#通过事件组织应用" class="headerlink" title="通过事件组织应用"></a>通过事件组织应用</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听用户注册成功消息，绑定邮件程序</span></span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> emails = <span class="built_in">require</span>(<span class="string">&#x27;./emails&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> routes = <span class="built_in">require</span>(<span class="string">&#x27;./routes&#x27;</span>);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">json</span>());</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/users&#x27;</span>, routes.<span class="property">users</span>.<span class="property">create</span>); <span class="comment">// 设置路由创建用户</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">on</span>(<span class="string">&#x27;user:created&#x27;</span>, emails.<span class="property">welcome</span>); <span class="comment">// 监听创建成功事件，绑定 email 代码</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = app;</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用户注册成功发起事件</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">User</span> = <span class="built_in">require</span>(<span class="string">&#x27;./../models/user&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">create</span> = <span class="keyword">function</span> (<span class="params">req, res, next</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="keyword">new</span> <span class="title class_">User</span>(req.<span class="property">body</span>);</span><br><span class="line">  user.<span class="title function_">save</span>(<span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="keyword">return</span> <span class="title function_">next</span>(err);</span><br><span class="line">    res.<span class="property">app</span>.<span class="title function_">emit</span>(<span class="string">&#x27;user:created&#x27;</span>, user); <span class="comment">// 当用户成功注册时触发创建用户事件</span></span><br><span class="line">    res.<span class="title function_">send</span>(<span class="string">&#x27;User created&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="WebSocket-与-session"><a href="#WebSocket-与-session" class="headerlink" title="WebSocket 与 session"></a>WebSocket 与 session</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">WebSocketServer</span> = <span class="built_in">require</span>(<span class="string">&#x27;ws&#x27;</span>).<span class="property">Server</span>;</span><br><span class="line"><span class="keyword">const</span> parseCookie = express.<span class="title function_">cookieParser</span>(<span class="string">&#x27;some secret&#x27;</span>); <span class="comment">// 加载解析 cookie 中间件，设置密码</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MemoryStore</span> = express.<span class="property">session</span>.<span class="property">MemoryStore</span>; <span class="comment">// 加载要使用的会话存储</span></span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> <span class="title class_">MemoryStore</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> server = app.<span class="title function_">listen</span>(process.<span class="property">env</span>.<span class="property">PORT</span> || <span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(parseCookie);</span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">session</span>(&#123; <span class="attr">store</span>: store, <span class="attr">secret</span>: <span class="string">&#x27;some secret&#x27;</span> &#125;)); <span class="comment">// 告知 Express 使用会话存储和设置密码(使用 session 中间件)</span></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(__dirname + <span class="string">&#x27;/public&#x27;</span>));</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/random&#x27;</span>, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123; <span class="comment">// 测试测试用的会话值</span></span><br><span class="line">  req.<span class="property">session</span>.<span class="property">random</span> = <span class="title class_">Math</span>.<span class="title function_">random</span>().<span class="title function_">toString</span>();</span><br><span class="line">  res.<span class="title function_">send</span>(<span class="number">200</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置 WebSocket 服务器，将其传递给 Express 服务器</span></span><br><span class="line"><span class="comment">// 需要传递已有的 Express 服务（listen 的返回对象）</span></span><br><span class="line"><span class="keyword">const</span> webSocketServer = <span class="keyword">new</span> <span class="title class_">WebSocketServer</span>(&#123; <span class="attr">server</span>: server &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在连接事件给客户端创建 WebSocket</span></span><br><span class="line">webSocketServer.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span> (<span class="params">ws</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> session;</span><br><span class="line"></span><br><span class="line">  ws.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">data, flags</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> message = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(data);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 客户端发送的 JSON，需要一些代码来解析 JSON 字符串确定是否可用</span></span><br><span class="line">    <span class="keyword">if</span> (message.<span class="property">type</span> === <span class="string">&#x27;getSession&#x27;</span>) &#123;</span><br><span class="line">      <span class="title function_">parseCookie</span>(ws.<span class="property">upgradeReq</span>, <span class="literal">null</span>, <span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">        <span class="comment">// 从 HTTP 的更新请求中获取 WebSocket 的会话 ID</span></span><br><span class="line">        <span class="comment">// 一旦 WebSockets 服务器有一个连接，session ID 可以用=从初始化请求中的 cookies 中获取</span></span><br><span class="line">        <span class="keyword">const</span> sid = ws.<span class="property">upgradeReq</span>.<span class="property">signedCookies</span>[<span class="string">&#x27;connect.sid&#x27;</span>];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 从存储中获取用户的会话信息</span></span><br><span class="line">        <span class="comment">// 只需要在初始化的请求中传递一个引用给解析 cookie 的中间件</span></span><br><span class="line">        <span class="comment">// 然后 session 可以使用 session 存储的 get 方法加载</span></span><br><span class="line">        store.<span class="title function_">get</span>(sid, <span class="keyword">function</span> (<span class="params">err, loadedSession</span>) &#123;</span><br><span class="line">          <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">          session = loadedSession;</span><br><span class="line">          ws.<span class="title function_">send</span>(<span class="string">&#x27;session.random: &#x27;</span> + session.<span class="property">random</span>, &#123;</span><br><span class="line">            <span class="attr">mask</span>: <span class="literal">false</span>,</span><br><span class="line">          &#125;); <span class="comment">// session 加载后会把一个包含了 session 值的消息发回给客户端</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      ws.<span class="title function_">send</span>(<span class="string">&#x27;Unknown command&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> host = <span class="variable language_">window</span>.<span class="property">document</span>.<span class="property">location</span>.<span class="property">host</span>.<span class="title function_">replace</span>(<span class="regexp">/:.*/</span>, <span class="string">&#x27;&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> ws = <span class="keyword">new</span> <span class="title class_">WebSocket</span>(<span class="string">&#x27;ws://&#x27;</span> + host + <span class="string">&#x27;:3000&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="built_in">setInterval</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      ws.<span class="title function_">send</span>(<span class="string">&#x27;&#123; &quot;type&quot;: &quot;getSession&quot; &#125;&#x27;</span>); <span class="comment">// 定期向服务器发送消息</span></span></span><br><span class="line"><span class="language-javascript">    &#125;, <span class="number">1000</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    ws.<span class="property">onmessage</span> = <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;message&#x27;</span>).<span class="property">innerHTML</span> = event.<span class="property">data</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;;</span></span><br><span class="line"><span class="language-javascript">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">h1</span>&gt;</span>WebSocket sessions<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&#x27;message&#x27;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h2 id="Express4-中间件"><a href="#Express4-中间件" class="headerlink" title="Express4 中间件"></a>Express4 中间件</h2><table>
<thead>
<tr>
<th>package</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>body-parser</td>
<td>解析 URL 编码 和 JSON POST 请求的 body 数据</td>
</tr>
<tr>
<td>compression</td>
<td>压缩服务器响应</td>
</tr>
<tr>
<td>connect-timeout</td>
<td>请求允许超时</td>
</tr>
<tr>
<td>cookie-parser</td>
<td>从 HTTP 头部信息中解析 cookies，结果放在 req.cookies</td>
</tr>
<tr>
<td>cookie-session</td>
<td>使用 cookies 来支持简单会话</td>
</tr>
<tr>
<td>csurf</td>
<td>在会话中添加 token，防御 CSRF 攻击</td>
</tr>
<tr>
<td>errorhandler</td>
<td>Connect 中使用的默认错误处理</td>
</tr>
<tr>
<td>express-session</td>
<td>简单的会话处理，使用 stores 扩展来吧会话信息写入到数据库或文件中</td>
</tr>
<tr>
<td>method-override</td>
<td>映射新的 HTTP 动词到请求变量中的 _method</td>
</tr>
<tr>
<td>morgan</td>
<td>日志格式化</td>
</tr>
<tr>
<td>response-time</td>
<td>跟踪响应时间</td>
</tr>
<tr>
<td>serve-favicon</td>
<td>发送网站图标</td>
</tr>
<tr>
<td>serve-index</td>
<td>目录列表</td>
</tr>
<tr>
<td>whost</td>
<td>允许路由匹配子域名</td>
</tr>
</tbody></table>
<h2 id="JWT"><a href="#JWT" class="headerlink" title="JWT"></a>JWT</h2><p>JSON Web Token（缩写 JWT）是目前最流行的跨域认证解决方案。</p>
<h3 id="跨域认证"><a href="#跨域认证" class="headerlink" title="跨域认证"></a>跨域认证</h3><h4 id="一般流程"><a href="#一般流程" class="headerlink" title="一般流程"></a>一般流程</h4><ul>
<li>用户向服务器发送用户名和密码</li>
<li>服务器验证通过后，在当前对话（session）里面保存相关数据，比如用户角色、登录时间等等</li>
<li>服务器向用户返回一个 session_id，写入用户的 Cookie</li>
<li>用户随后的每一次请求，都会通过 Cookie，将 session_id 传回服务器</li>
<li>服务器收到 session_id，找到前期保存的数据，由此得知用户的身份</li>
</ul>
<h4 id="session-共享"><a href="#session-共享" class="headerlink" title="session 共享"></a>session 共享</h4><p>在服务器集群，要求 session 数据共享，每台服务器都能够读取 session：</p>
<ul>
<li>一种解决方案是 session 数据持久化，写入数据库或别的持久层。各种服务收到请求后，都向持久层请求数据。这种方案的优点是架构清晰，缺点是工程量比较大。另外，持久层万一挂了，就会单点失败。</li>
<li>另一种方案是服务器索性不保存 session 数据了，所有数据都保存在客户端，每次请求都发回服务器。JWT 就是这种方案的一个代表。</li>
</ul>
<h3 id="JWT-1"><a href="#JWT-1" class="headerlink" title="JWT"></a>JWT</h3><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><ul>
<li>服务器认证以后，生成一个 JSON 对象，发回给用户</li>
<li>用户与服务端通信的时候，都要发回这个 JSON 对象，服务器完全只靠这个对象认定用户身份</li>
<li>防止篡改会加上签名</li>
</ul>
<h4 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h4><p>Header（头部）.Payload（负载）.Signature（签名）：</p>
<ul>
<li>Header：JSON，使用 Base64 URL 转成字符串</li>
<li>Payload：JSON，使用 Base64 URL 转成字符串</li>
<li>Signature：对前两部分的签名</li>
</ul>
<h5 id="Header"><a href="#Header" class="headerlink" title="Header"></a>Header</h5><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;alg&quot;</span>: <span class="string">&quot;HS256&quot;</span>, <span class="comment">// 签名的算法</span></span><br><span class="line">  <span class="string">&quot;typ&quot;</span>: <span class="string">&quot;JWT&quot;</span> <span class="comment">// token 的类型</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="Payload"><a href="#Payload" class="headerlink" title="Payload"></a>Payload</h5><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// 7 个官方字段</span></span><br><span class="line">  <span class="string">&quot;iss&quot;</span>: <span class="string">&quot;签发人&quot;</span>,</span><br><span class="line">  <span class="string">&quot;exp&quot;</span>: <span class="string">&quot;过期时间&quot;</span>,</span><br><span class="line">  <span class="string">&quot;sub&quot;</span>: <span class="string">&quot;主题&quot;</span>,</span><br><span class="line">  <span class="string">&quot;aud&quot;</span>: <span class="string">&quot;受众&quot;</span>,</span><br><span class="line">  <span class="string">&quot;nbf&quot;</span>: <span class="string">&quot;生效时间&quot;</span>,</span><br><span class="line">  <span class="string">&quot;iat&quot;</span>: <span class="string">&quot;签发时间&quot;</span>,</span><br><span class="line">  <span class="string">&quot;jti&quot;</span>: <span class="string">&quot;编号&quot;</span>,</span><br><span class="line">  <span class="comment">// 定义私有字段</span></span><br><span class="line">  <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Chenng&quot;</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="Signature"><a href="#Signature" class="headerlink" title="Signature"></a>Signature</h5><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">HMACSHA256(</span><br><span class="line">  base64UrlEncode(header) + <span class="string">&quot;.&quot;</span> +</span><br><span class="line">  base64UrlEncode(payload),</span><br><span class="line">  secret) <span class="comment"># secret 秘钥只有服务器知道</span></span><br></pre></td></tr></table></figure>


<h4 id="使用方式"><a href="#使用方式" class="headerlink" title="使用方式"></a>使用方式</h4><ul>
<li>客户端收到服务器返回的 JWT，可以储存在 Cookie 里面，也可以储存在 localStorage</li>
<li>放在 Cookie 里面自动发送，但是这样不能跨域，所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面</li>
</ul>
<h4 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h4><ul>
<li>JWT 不仅可以用于认证，也可以用于交换信息。有效使用 JWT，可以降低服务器查询数据库的次数</li>
<li>JWT 的最大缺点是，由于服务器不保存 session 状态，因此无法在使用过程中废止某个 token，或者更改 token 的权限。也就是说，一旦 JWT 签发了，在到期之前就会始终有效，除非服务器部署额外的逻辑</li>
<li>JWT 本身包含了认证信息，一旦泄露，任何人都可以获得该令牌的所有权限。为了减少盗用，JWT 的有效期应该设置得比较短。对于一些比较重要的权限，使用时应该再次对用户进行认证</li>
</ul>
<h2 id="koa"><a href="#koa" class="headerlink" title="koa"></a>koa</h2><h3 id="核心对象"><a href="#核心对象" class="headerlink" title="核心对象"></a>核心对象</h3><ul>
<li>HTTP 接收 解析 响应</li>
<li>中间件 执行上下文</li>
<li>Koa 中一切的流程都是中间件</li>
</ul>
<h3 id="源码组成"><a href="#源码组成" class="headerlink" title="源码组成"></a>源码组成</h3><ul>
<li>application</li>
<li>context</li>
<li>request</li>
<li>response</li>
</ul>
<h3 id="中间件的使用"><a href="#中间件的使用" class="headerlink" title="中间件的使用"></a>中间件的使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Koa</span> = <span class="built_in">require</span>(<span class="string">&#x27;koa&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">Koa</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">mid1</span> = <span class="keyword">async</span> (<span class="params">ctx, next</span>) =&gt; &#123;</span><br><span class="line">  ctx.<span class="property">body</span> = <span class="string">&#x27;Hi&#x27;</span>;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>(); <span class="comment">// next 执行下一个中间件</span></span><br><span class="line">  ctx.<span class="property">body</span> += <span class="string">&#x27; there&#x27;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">mid2</span> = <span class="keyword">async</span> (<span class="params">ctx, next</span>) =&gt; &#123;</span><br><span class="line">  ctx.<span class="property">type</span> = <span class="string">&#x27;text/html; chartset=utf-8&#x27;</span>;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">mid3</span> = <span class="keyword">async</span> (<span class="params">ctx, next</span>) =&gt; &#123;</span><br><span class="line">  ctx.<span class="property">body</span> += <span class="string">&#x27; chenng&#x27;</span>;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(mid1);</span><br><span class="line">app.<span class="title function_">use</span>(mid2);</span><br><span class="line">app.<span class="title function_">use</span>(mid3);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">2333</span>);</span><br><span class="line"><span class="comment">// Hi chenng there</span></span><br></pre></td></tr></table></figure>

<h3 id="返回媒体资源"><a href="#返回媒体资源" class="headerlink" title="返回媒体资源"></a>返回媒体资源</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">router</span><br><span class="line">  .<span class="title function_">get</span>(<span class="string">&#x27;/api/dynamic_image/codewars&#x27;</span>, <span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> axios.<span class="title function_">get</span>(<span class="string">&#x27;https://www.codewars.com/users/ringcrl&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> [, kyu, score] = res.<span class="property">data</span></span><br><span class="line">      .<span class="title function_">match</span>(<span class="regexp">/&lt;div class=&quot;stat&quot;&gt;&lt;b&gt;Rank:&lt;\/b&gt;(.+?)&lt;\/div&gt;&lt;div class=&quot;stat&quot;&gt;&lt;b&gt;Honor:&lt;\/b&gt;(.+?)&lt;\/div&gt;/</span>);</span><br><span class="line">    <span class="keyword">const</span> svg = <span class="string">`</span></span><br><span class="line"><span class="string">      &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;80&quot; height=&quot;20&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;80&quot; height=&quot;20&quot; fill=&quot;#fff&quot; stroke-width=&quot;2&quot; stroke=&quot;#cccccc&quot;&gt;&lt;/rect&gt;</span></span><br><span class="line"><span class="string">        &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;50&quot; height=&quot;20&quot; fill=&quot;#5b5b5b&quot;&gt;&lt;/rect&gt;</span></span><br><span class="line"><span class="string">        &lt;text x=&quot;5&quot; y=&quot;15&quot; class=&quot;small&quot; fill=&quot;#fff&quot; style=&quot;font-size: 14px;&quot;&gt;<span class="subst">$&#123;kyu&#125;</span>&lt;/text&gt;</span></span><br><span class="line"><span class="string">        &lt;rect x=&quot;50&quot; y=&quot;0&quot; width=&quot;30&quot; height=&quot;20&quot; fill=&quot;#3275b0&quot;&gt;&lt;/rect&gt;</span></span><br><span class="line"><span class="string">        &lt;text x=&quot;53&quot; y=&quot;15&quot; class=&quot;small&quot; fill=&quot;#fff&quot; style=&quot;font-size: 14px&quot;&gt;<span class="subst">$&#123;score&#125;</span>&lt;/text&gt;</span></span><br><span class="line"><span class="string">      &lt;/svg&gt;</span></span><br><span class="line"><span class="string">    `</span>;</span><br><span class="line">    ctx.<span class="title function_">set</span>(<span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;image/svg+xml&#x27;</span>);</span><br><span class="line">    ctx.<span class="property">body</span> = <span class="title class_">Buffer</span>.<span class="title function_">from</span>(svg);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>

<h2 id="Web-API-设计"><a href="#Web-API-设计" class="headerlink" title="Web API 设计"></a>Web API 设计</h2><h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><ul>
<li>易于使用</li>
<li>便于修改</li>
<li>健壮性好</li>
<li>不怕公之于众</li>
</ul>
<h3 id="重要准则"><a href="#重要准则" class="headerlink" title="重要准则"></a>重要准则</h3><ul>
<li>设计容易记忆、功能一目了然</li>
<li>使用合适的 HTTP 方法</li>
<li>选择合适的英语单词，注意单词的单复数形式</li>
<li>使用 OAuth 2.0 进行认证</li>
</ul>
<p>API 通用资源网站 ProgrammableWeb（<a target="_blank" rel="noopener" href="http://www.programmableweb.com/">http://www.programmableweb.com</a>）中有各种已经公开的 Web API 文档，多观察一下</p>
<h2 id="公钥加密私钥解密"><a href="#公钥加密私钥解密" class="headerlink" title="公钥加密私钥解密"></a>公钥加密私钥解密</h2><h3 id="生成公钥私钥"><a href="#生成公钥私钥" class="headerlink" title="生成公钥私钥"></a>生成公钥私钥</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">利用 openssl 生成公钥私钥 </span><br><span class="line">生成公钥：openssl genrsa -out rsa_private_key.pem 1024 </span><br><span class="line">生成私钥：openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem</span><br></pre></td></tr></table></figure>

<h3 id="crypto-使用"><a href="#crypto-使用" class="headerlink" title="crypto 使用"></a>crypto 使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> publicKey = fs.<span class="title function_">readFileSync</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/rsa_public_key.pem`</span>).<span class="title function_">toString</span>(<span class="string">&#x27;ascii&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> privateKey = fs.<span class="title function_">readFileSync</span>(<span class="string">`<span class="subst">$&#123;__dirname&#125;</span>/rsa_private_key.pem`</span>).<span class="title function_">toString</span>(<span class="string">&#x27;ascii&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(publicKey);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(privateKey);</span><br><span class="line"><span class="keyword">const</span> data = <span class="string">&#x27;Chenng&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;content: &#x27;</span>, data);</span><br><span class="line"></span><br><span class="line"><span class="comment">//公钥加密</span></span><br><span class="line"><span class="keyword">const</span> encodeData = crypto.<span class="title function_">publicEncrypt</span>(</span><br><span class="line">  publicKey,</span><br><span class="line">  <span class="title class_">Buffer</span>.<span class="title function_">from</span>(data),</span><br><span class="line">).<span class="title function_">toString</span>(<span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;encode: &#x27;</span>, encodeData);</span><br><span class="line"></span><br><span class="line"><span class="comment">//私钥解密</span></span><br><span class="line"><span class="keyword">const</span> decodeData = crypto.<span class="title function_">privateDecrypt</span>(</span><br><span class="line">  privateKey,</span><br><span class="line">  <span class="title class_">Buffer</span>.<span class="title function_">from</span>(encodeData, <span class="string">&#x27;base64&#x27;</span>),</span><br><span class="line">);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;decode: &#x27;</span>, decodeData.<span class="title function_">toString</span>());</span><br></pre></td></tr></table></figure>

<h2 id="redis-缓存接口"><a href="#redis-缓存接口" class="headerlink" title="redis 缓存接口"></a>redis 缓存接口</h2><ul>
<li>部分不用实时更新的数据使用 redis 进行缓存</li>
<li>使用 node-schedule 在每晚定时调用接口</li>
</ul>
<h3 id="redis-使用"><a href="#redis-使用" class="headerlink" title="redis 使用"></a>redis 使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> redis = <span class="built_in">require</span>(<span class="string">&#x27;redis&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> redisClient = redis.<span class="title function_">createClient</span>();</span><br><span class="line"><span class="keyword">const</span> getAsync = <span class="title function_">promisify</span>(redisClient.<span class="property">get</span>).<span class="title function_">bind</span>(redisClient);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> codewarsRes = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="keyword">await</span> <span class="title function_">getAsync</span>(<span class="string">&#x27;codewarsRes&#x27;</span>));</span><br><span class="line"><span class="keyword">if</span> (!codewarsRes) &#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> axios.<span class="title function_">get</span>(<span class="string">&#x27;https://www.codewars.com/users/ringcrl&#x27;</span>);</span><br><span class="line">  codewarsRes = res.<span class="property">data</span>;</span><br><span class="line">  redisClient.<span class="title function_">set</span>(<span class="string">&#x27;codewarsRes&#x27;</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(codewarsRes), <span class="string">&#x27;EX&#x27;</span>, <span class="number">86000</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="node-schedule-使用"><a href="#node-schedule-使用" class="headerlink" title="node-schedule 使用"></a>node-schedule 使用</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> schedule = <span class="built_in">require</span>(<span class="string">&#x27;node-schedule&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">&#x27;axios&#x27;</span>);</span><br><span class="line"></span><br><span class="line">schedule.<span class="title function_">scheduleJob</span>(<span class="string">&#x27;* 23 59 * *&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  axios.<span class="title function_">get</span>(<span class="string">&#x27;https://static.chenng.cn/api/dynamic_image/leetcode_problems&#x27;</span>);</span><br><span class="line">  axios.<span class="title function_">get</span>(<span class="string">&#x27;https://static.chenng.cn/api/dynamic_image/leetcode&#x27;</span>);</span><br><span class="line">  axios.<span class="title function_">get</span>(<span class="string">&#x27;https://static.chenng.cn/api/dynamic_image/codewars&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="跨域共享-Cookie"><a href="#跨域共享-Cookie" class="headerlink" title="跨域共享 Cookie"></a>跨域共享 Cookie</h2><h3 id="二级域名共享-Cookie"><a href="#二级域名共享-Cookie" class="headerlink" title="二级域名共享 Cookie"></a>二级域名共享 Cookie</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">session</span>(&#123;</span><br><span class="line">  <span class="attr">secret</span>: conf.<span class="property">secret</span>,</span><br><span class="line">  <span class="attr">maxAge</span>: <span class="keyword">new</span> <span class="title class_">Date</span>(<span class="title class_">Date</span>.<span class="title function_">now</span>() + <span class="number">3600000</span>),</span><br><span class="line">  <span class="attr">cookie</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">    <span class="attr">domain</span>: <span class="string">&#x27;.yourdomain.com&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">store</span>: <span class="keyword">new</span> <span class="title class_">MongoStore</span>(conf.<span class="property">sessiondb</span>),</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>

<h3 id="淘宝天猫共享-Cookie"><a href="#淘宝天猫共享-Cookie" class="headerlink" title="淘宝天猫共享 Cookie"></a>淘宝天猫共享 Cookie</h3><ul>
<li>淘宝登录后 <code>.taobao.com</code> 的接口会带上 Cookie</li>
<li>天猫使用 JSONP 请求一个 <code>.taobao.com</code> 的接口，接口返回一段 script，直接把带有 Cookie 的对象设置到 window 下</li>
</ul>
<h1 id="参考地址"><a href="#参考地址" class="headerlink" title="参考地址"></a>参考地址</h1><ul>
<li><a target="_blank" rel="noopener" href="https://www.amazon.cn/dp/B01MYX8XG1">《Node.js硬实战：115个核心技巧》</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/i0natan/nodebestpractices">i0natan&#x2F;nodebestpractices</a></li>
<li><a target="_blank" rel="noopener" href="https://juejin.im/post/5c63b5676fb9a049ac79a798?utm_source=gold_browser_extension">真-Node多线程</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/CyC2018/CS-Notes">CS-Notes</a></li>
</ul>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="http://example.com">lzoxun</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://example.com/2023/07/22/server/node/%E5%88%AB%E4%BA%BA%E7%9A%84%E6%96%87%E6%A1%A3/">http://example.com/2023/07/22/server/node/%E5%88%AB%E4%BA%BA%E7%9A%84%E6%96%87%E6%A1%A3/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://example.com" target="_blank">学习笔记</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"></div><div class="post_share"><div class="social-share" data-image="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2023/07/22/server/node/server-nodejs/" title="nodejs基础"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">nodejs基础</div></div></a></div><div class="next-post pull-right"><a href="/2023/07/22/server/node/node-apidoc/" title="接口文档"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">接口文档</div></div></a></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "" data-lazy-src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">lzoxun</div><div class="author-info__description"></div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">108</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">1</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/liaozhongxun"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/liaozhongxun" target="_blank" title="Github"><i class="fab fa-github" style="color: #24292e;"></i></a><a class="social-icon" href="mailto:869664233@gmail.com" target="_blank" title="Email"><i class="fas fa-envelope" style="color: #4a7dbe;"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">这是我的文档</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#docsify-%E6%96%87%E6%A1%A3"><span class="toc-number">1.</span> <span class="toc-text">docsify 文档</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95"><span class="toc-number">2.</span> <span class="toc-text">更新记录</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E8%AF%B4%E6%98%8E"><span class="toc-number">3.</span> <span class="toc-text">说明</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%AE%89%E8%A3%85"><span class="toc-number">4.</span> <span class="toc-text">安装</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F"><span class="toc-number">5.</span> <span class="toc-text">全局变量</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#require-id"><span class="toc-number">5.1.</span> <span class="toc-text">require(id)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#module-exports"><span class="toc-number">5.1.1.</span> <span class="toc-text">module.exports</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#JSON-%E6%96%87%E4%BB%B6"><span class="toc-number">5.1.2.</span> <span class="toc-text">JSON 文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E5%A4%A7%E6%96%87%E4%BB%B6"><span class="toc-number">5.1.3.</span> <span class="toc-text">加载大文件</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#module-exports-%E5%92%8C-exports"><span class="toc-number">5.2.</span> <span class="toc-text">module.exports 和 exports</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%89%A7%E8%A1%8C%E6%97%B6"><span class="toc-number">5.2.1.</span> <span class="toc-text">执行时</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#exports"><span class="toc-number">5.2.2.</span> <span class="toc-text">exports</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8"><span class="toc-number">5.2.3.</span> <span class="toc-text">使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B7%AF%E5%BE%84%E5%8F%98%E9%87%8F"><span class="toc-number">5.3.</span> <span class="toc-text">路径变量</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#console"><span class="toc-number">5.4.</span> <span class="toc-text">console</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#process"><span class="toc-number">5.5.</span> <span class="toc-text">process</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9F%A5%E7%9C%8B-PATH"><span class="toc-number">5.5.1.</span> <span class="toc-text">查看 PATH</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%AE%BE%E7%BD%AE-PATH"><span class="toc-number">5.5.2.</span> <span class="toc-text">设置 PATH</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E4%BF%A1%E6%81%AF"><span class="toc-number">5.5.3.</span> <span class="toc-text">获取信息</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#nextTick"><span class="toc-number">5.5.4.</span> <span class="toc-text">nextTick</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Buffer"><span class="toc-number">6.</span> <span class="toc-text">Buffer</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#toString-NaN"><span class="toc-number">6.1.</span> <span class="toc-text">toString</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#data-URI"><span class="toc-number">6.2.</span> <span class="toc-text">data URI</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#events"><span class="toc-number">7.</span> <span class="toc-text">events</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#util"><span class="toc-number">8.</span> <span class="toc-text">util</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#promisify"><span class="toc-number">8.1.</span> <span class="toc-text">promisify</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%B5%81"><span class="toc-number">9.</span> <span class="toc-text">流</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%90%86%E8%A7%A3%E6%B5%81"><span class="toc-number">9.1.</span> <span class="toc-text">理解流</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B5%81%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="toc-number">9.1.1.</span> <span class="toc-text">流的类型</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%86%85%E5%BB%BA%E6%B5%81-API"><span class="toc-number">9.2.</span> <span class="toc-text">使用内建流 API</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9D%99%E6%80%81-web-%E6%9C%8D%E5%8A%A1%E5%99%A8"><span class="toc-number">9.2.1.</span> <span class="toc-text">静态 web 服务器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%B5%81"><span class="toc-number">9.2.1.1.</span> <span class="toc-text">不使用流</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%B5%81"><span class="toc-number">9.2.1.2.</span> <span class="toc-text">使用流</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%B5%81-gzip"><span class="toc-number">9.2.1.3.</span> <span class="toc-text">使用流 + gzip</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B5%81%E7%9A%84%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86"><span class="toc-number">9.2.2.</span> <span class="toc-text">流的错误处理</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%B5%81%E5%9F%BA%E7%B1%BB"><span class="toc-number">9.3.</span> <span class="toc-text">使用流基类</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%AF%E8%AF%BB%E6%B5%81-JSON-%E8%A1%8C%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">9.3.1.</span> <span class="toc-text">可读流 - JSON 行解析器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%AF%E5%86%99%E6%B5%81-%E6%96%87%E5%AD%97%E5%8F%98%E8%89%B2"><span class="toc-number">9.3.2.</span> <span class="toc-text">可写流 - 文字变色</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%8C%E5%B7%A5%E6%B5%81-%E6%8E%A5%E5%8F%97%E5%92%8C%E8%BD%AC%E6%8D%A2%E6%95%B0%E6%8D%AE"><span class="toc-number">9.3.3.</span> <span class="toc-text">双工流 - 接受和转换数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%AC%E6%8D%A2%E6%B5%81-%E8%A7%A3%E6%9E%90%E6%95%B0%E6%8D%AE"><span class="toc-number">9.3.4.</span> <span class="toc-text">转换流 - 解析数据</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B5%8B%E8%AF%95%E6%B5%81"><span class="toc-number">9.4.</span> <span class="toc-text">测试流</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"><span class="toc-number">10.</span> <span class="toc-text">文件系统</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#fs-%E6%A8%A1%E5%9D%97%E4%BA%A4%E4%BA%92"><span class="toc-number">10.1.</span> <span class="toc-text">fs 模块交互</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#POSIX-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"><span class="toc-number">10.2.</span> <span class="toc-text">POSIX 文件系统</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%AF%BB%E5%86%99%E6%B5%81"><span class="toc-number">10.3.</span> <span class="toc-text">读写流</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%96%87%E4%BB%B6%E7%9B%91%E6%8E%A7"><span class="toc-number">10.4.</span> <span class="toc-text">文件监控</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%90%8C%E6%AD%A5%E8%AF%BB%E5%8F%96%E4%B8%8E-require"><span class="toc-number">10.5.</span> <span class="toc-text">同步读取与 require</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0"><span class="toc-number">10.6.</span> <span class="toc-text">文件描述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%96%87%E4%BB%B6%E9%94%81"><span class="toc-number">10.7.</span> <span class="toc-text">文件锁</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%8B%AC%E5%8D%A0%E6%A0%87%E8%AE%B0"><span class="toc-number">10.7.1.</span> <span class="toc-text">独占标记</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#mkdir-%E6%96%87%E4%BB%B6%E9%94%81"><span class="toc-number">10.7.2.</span> <span class="toc-text">mkdir 文件锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#lock-%E6%A8%A1%E5%9D%97%E5%AE%9E%E7%8E%B0"><span class="toc-number">10.7.3.</span> <span class="toc-text">lock 模块实现</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%92%E5%BD%92%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C"><span class="toc-number">10.8.</span> <span class="toc-text">递归文件操作</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9B%91%E8%A7%86%E6%96%87%E4%BB%B6%E5%92%8C%E6%96%87%E4%BB%B6%E5%A4%B9"><span class="toc-number">10.9.</span> <span class="toc-text">监视文件和文件夹</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%90%E8%A1%8C%E5%9C%B0%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6%E6%B5%81"><span class="toc-number">10.10.</span> <span class="toc-text">逐行地读取文件流</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E7%BD%91%E7%BB%9C"><span class="toc-number">11.</span> <span class="toc-text">网络</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E6%9C%AC%E5%9C%B0-IP"><span class="toc-number">11.1.</span> <span class="toc-text">获取本地 IP</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#TCP-%E5%AE%A2%E6%88%B7%E7%AB%AF"><span class="toc-number">11.2.</span> <span class="toc-text">TCP 客户端</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%AF%E5%8A%A8%E4%B8%8E%E6%B5%8B%E8%AF%95-TCP"><span class="toc-number">11.2.1.</span> <span class="toc-text">启动与测试 TCP</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#UDP-%E5%AE%A2%E6%88%B7%E7%AB%AF"><span class="toc-number">11.3.</span> <span class="toc-text">UDP 客户端</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%87%E4%BB%B6%E5%8F%91%E9%80%81%E6%9C%8D%E5%8A%A1"><span class="toc-number">11.3.1.</span> <span class="toc-text">文件发送服务</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#HTTP-%E5%AE%A2%E6%88%B7%E7%AB%AF"><span class="toc-number">11.4.</span> <span class="toc-text">HTTP 客户端</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%AF%E5%8A%A8%E4%B8%8E%E6%B5%8B%E8%AF%95-HTTP"><span class="toc-number">11.4.1.</span> <span class="toc-text">启动与测试 HTTP</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%87%8D%E5%AE%9A%E5%90%91"><span class="toc-number">11.4.2.</span> <span class="toc-text">重定向</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#HTTP-%E4%BB%A3%E7%90%86"><span class="toc-number">11.4.3.</span> <span class="toc-text">HTTP 代理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B0%81%E8%A3%85-request-promise"><span class="toc-number">11.4.4.</span> <span class="toc-text">封装 request-promise</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#DNS-%E8%AF%B7%E6%B1%82"><span class="toc-number">11.5.</span> <span class="toc-text">DNS 请求</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#crypto-%E5%BA%93%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86"><span class="toc-number">11.6.</span> <span class="toc-text">crypto 库加密解密</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%91%E8%B5%B7-HTTP-%E8%AF%B7%E6%B1%82%E7%9A%84%E6%96%B9%E6%B3%95"><span class="toc-number">11.7.</span> <span class="toc-text">发起 HTTP 请求的方法</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%AD%90%E8%BF%9B%E7%A8%8B"><span class="toc-number">12.</span> <span class="toc-text">子进程</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%89%A7%E8%A1%8C%E5%A4%96%E9%83%A8%E5%BA%94%E7%94%A8"><span class="toc-number">12.1.</span> <span class="toc-text">执行外部应用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="toc-number">12.1.1.</span> <span class="toc-text">基本概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#execFile"><span class="toc-number">12.1.2.</span> <span class="toc-text">execFile</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#spawn"><span class="toc-number">12.1.3.</span> <span class="toc-text">spawn</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8D%95%E4%B8%80%E4%BB%BB%E5%8A%A1"><span class="toc-number">12.1.3.1.</span> <span class="toc-text">单一任务</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%9A%E4%BB%BB%E5%8A%A1%E4%B8%B2%E8%81%94"><span class="toc-number">12.1.3.2.</span> <span class="toc-text">多任务串联</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#exec"><span class="toc-number">12.1.4.</span> <span class="toc-text">exec</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#fork"><span class="toc-number">12.1.5.</span> <span class="toc-text">fork</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%88%B6%E5%AD%90%E9%80%9A%E4%BF%A1"><span class="toc-number">12.1.5.1.</span> <span class="toc-text">父子通信</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B8%B8%E7%94%A8%E6%8A%80%E5%B7%A7"><span class="toc-number">12.2.</span> <span class="toc-text">常用技巧</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%80%80%E5%87%BA%E6%97%B6%E6%9D%80%E6%AD%BB%E6%89%80%E6%9C%89%E5%AD%90%E8%BF%9B%E7%A8%8B"><span class="toc-number">12.2.1.</span> <span class="toc-text">退出时杀死所有子进程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Cluster-%E7%9A%84%E7%90%86%E8%A7%A3"><span class="toc-number">12.3.</span> <span class="toc-text">Cluster 的理解</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Node-%E5%A4%9A%E7%BA%BF%E7%A8%8B"><span class="toc-number">13.</span> <span class="toc-text">Node 多线程</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8D%95%E7%BA%BF%E7%A8%8B%E9%97%AE%E9%A2%98"><span class="toc-number">13.1.</span> <span class="toc-text">单线程问题</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Node-%E7%BA%BF%E7%A8%8B"><span class="toc-number">13.2.</span> <span class="toc-text">Node 线程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BC%82%E6%AD%A5-IO"><span class="toc-number">13.3.</span> <span class="toc-text">异步 IO</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#cluster-%E5%A4%9A%E8%BF%9B%E7%A8%8B"><span class="toc-number">13.4.</span> <span class="toc-text">cluster 多进程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9C%9F-Node-%E5%A4%9A%E7%BA%BF%E7%A8%8B"><span class="toc-number">13.5.</span> <span class="toc-text">真 Node 多线程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1"><span class="toc-number">13.5.1.</span> <span class="toc-text">线程通信</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%A4%9A%E8%BF%9B%E7%A8%8B-vs-%E5%A4%9A%E7%BA%BF%E7%A8%8B"><span class="toc-number">13.6.</span> <span class="toc-text">多进程 vs 多线程</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F"><span class="toc-number">14.</span> <span class="toc-text">分布式</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="toc-number">14.1.</span> <span class="toc-text">分布式锁</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%94%AF%E4%B8%80%E7%B4%A2%E5%BC%95"><span class="toc-number">14.1.1.</span> <span class="toc-text">数据库的唯一索引</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Redis-%E7%9A%84-SETNX-%E6%8C%87%E4%BB%A4"><span class="toc-number">14.1.2.</span> <span class="toc-text">Redis 的 SETNX 指令</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Redis-%E7%9A%84-RedLock-%E7%AE%97%E6%B3%95"><span class="toc-number">14.1.3.</span> <span class="toc-text">Redis 的 RedLock 算法</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86"><span class="toc-number">15.</span> <span class="toc-text">项目管理</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BB%84%E4%BB%B6%E5%BC%8F%E6%9E%84%E5%BB%BA"><span class="toc-number">15.1.</span> <span class="toc-text">组件式构建</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%A4%9A%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE"><span class="toc-number">15.2.</span> <span class="toc-text">多环境配置</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86"><span class="toc-number">15.3.</span> <span class="toc-text">依赖管理</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86"><span class="toc-number">16.</span> <span class="toc-text">异常处理</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%A4%84%E7%90%86%E6%9C%AA%E6%8D%95%E8%8E%B7%E7%9A%84%E5%BC%82%E5%B8%B8"><span class="toc-number">16.1.</span> <span class="toc-text">处理未捕获的异常</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E8%BF%87-domain-%E7%AE%A1%E7%90%86%E5%BC%82%E5%B8%B8"><span class="toc-number">16.2.</span> <span class="toc-text">通过 domain 管理异常</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Joi-%E9%AA%8C%E8%AF%81%E5%8F%82%E6%95%B0"><span class="toc-number">16.3.</span> <span class="toc-text">Joi 验证参数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Kibana-%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7"><span class="toc-number">16.4.</span> <span class="toc-text">Kibana 系统监控</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%B8%8A%E7%BA%BF%E5%AE%9E%E8%B7%B5"><span class="toc-number">17.</span> <span class="toc-text">上线实践</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-winston-%E8%AE%B0%E5%BD%95%E6%97%A5%E8%AE%B0"><span class="toc-number">17.1.</span> <span class="toc-text">使用 winston 记录日记</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%A7%94%E6%89%98%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86"><span class="toc-number">17.2.</span> <span class="toc-text">委托反向代理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A3%80%E6%B5%8B%E6%9C%89%E6%BC%8F%E6%B4%9E%E7%9A%84%E4%BE%9D%E8%B5%96%E9%A1%B9"><span class="toc-number">17.3.</span> <span class="toc-text">检测有漏洞的依赖项</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#PM2-HTTP-%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE"><span class="toc-number">17.4.</span> <span class="toc-text">PM2 HTTP 集群配置</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B7%A5%E4%BD%9C%E7%BA%BF%E7%A8%8B%E9%85%8D%E7%BD%AE"><span class="toc-number">17.4.1.</span> <span class="toc-text">工作线程配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#PM2-%E8%87%AA%E5%8A%A8%E5%90%AF%E5%8A%A8"><span class="toc-number">17.4.2.</span> <span class="toc-text">PM2 自动启动</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%80%A7%E8%83%BD%E5%AE%9E%E8%B7%B5"><span class="toc-number">18.</span> <span class="toc-text">性能实践</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8-Lodash"><span class="toc-number">18.1.</span> <span class="toc-text">避免使用 Lodash</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#benchmark"><span class="toc-number">18.2.</span> <span class="toc-text">benchmark</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-prof-%E8%BF%9B%E8%A1%8C%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90"><span class="toc-number">18.3.</span> <span class="toc-text">使用 prof 进行性能分析</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-headdump-%E5%A0%86%E5%BF%AB%E7%85%A7"><span class="toc-number">18.4.</span> <span class="toc-text">使用 headdump 堆快照</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8%E6%B8%85%E5%8D%95"><span class="toc-number">19.</span> <span class="toc-text">应用安全清单</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#helmet-%E8%AE%BE%E7%BD%AE%E5%AE%89%E5%85%A8%E5%93%8D%E5%BA%94%E5%A4%B4"><span class="toc-number">19.1.</span> <span class="toc-text">helmet 设置安全响应头</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-security-linter-%E6%8F%92%E4%BB%B6"><span class="toc-number">19.2.</span> <span class="toc-text">使用 security-linter 插件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#koa-ratelimit-%E9%99%90%E5%88%B6%E5%B9%B6%E5%8F%91%E8%AF%B7%E6%B1%82"><span class="toc-number">19.3.</span> <span class="toc-text">koa-ratelimit 限制并发请求</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BA%AF%E6%96%87%E6%9C%AC%E6%9C%BA%E5%AF%86%E4%BF%A1%E6%81%AF%E6%94%BE%E7%BD%AE"><span class="toc-number">19.4.</span> <span class="toc-text">纯文本机密信息放置</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ORM-ODM-%E5%BA%93%E9%98%B2%E6%AD%A2%E6%9F%A5%E8%AF%A2%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E"><span class="toc-number">19.5.</span> <span class="toc-text">ORM&#x2F;ODM 库防止查询注入漏洞</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-Bcrypt-%E4%BB%A3%E6%9B%BF-Crypto"><span class="toc-number">19.6.</span> <span class="toc-text">使用 Bcrypt 代替 Crypto</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%BD%AC%E4%B9%89-HTML%E3%80%81JS-%E5%92%8C-CSS-%E8%BE%93%E5%87%BA"><span class="toc-number">19.7.</span> <span class="toc-text">转义 HTML、JS 和 CSS 输出</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%AA%8C%E8%AF%81%E4%BC%A0%E5%85%A5%E7%9A%84-JSON-schemas"><span class="toc-number">19.8.</span> <span class="toc-text">验证传入的 JSON schemas</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%94%AF%E6%8C%81%E9%BB%91%E5%90%8D%E5%8D%95%E7%9A%84-JWT"><span class="toc-number">19.9.</span> <span class="toc-text">支持黑名单的 JWT</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%99%90%E5%88%B6%E6%AF%8F%E4%B8%AA%E7%94%A8%E6%88%B7%E5%85%81%E8%AE%B8%E7%9A%84%E7%99%BB%E5%BD%95%E8%AF%B7%E6%B1%82"><span class="toc-number">19.10.</span> <span class="toc-text">限制每个用户允许的登录请求</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E9%9D%9E-root-%E7%94%A8%E6%88%B7%E8%BF%90%E8%A1%8C-Node-js"><span class="toc-number">19.11.</span> <span class="toc-text">使用非 root 用户运行 Node.js</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E6%88%96%E4%B8%AD%E9%97%B4%E4%BB%B6%E9%99%90%E5%88%B6%E8%B4%9F%E8%BD%BD%E5%A4%A7%E5%B0%8F"><span class="toc-number">19.12.</span> <span class="toc-text">使用反向代理或中间件限制负载大小</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%98%B2%E6%AD%A2-RegEx-%E8%AE%A9-NodeJS-%E8%BF%87%E8%BD%BD"><span class="toc-number">19.13.</span> <span class="toc-text">防止 RegEx 让 NodeJS 过载</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9C%A8%E6%B2%99%E7%AE%B1%E4%B8%AD%E8%BF%90%E8%A1%8C%E4%B8%8D%E5%AE%89%E5%85%A8%E4%BB%A3%E7%A0%81"><span class="toc-number">19.14.</span> <span class="toc-text">在沙箱中运行不安全代码</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%9A%90%E8%97%8F%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%E9%94%99%E8%AF%AF%E8%AF%A6%E7%BB%86%E4%BF%A1%E6%81%AF"><span class="toc-number">19.15.</span> <span class="toc-text">隐藏客户端的错误详细信息</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%AF%B9-npm-%E6%88%96-Yarn%EF%BC%8C%E9%85%8D%E7%BD%AE-2FA"><span class="toc-number">19.16.</span> <span class="toc-text">对 npm 或 Yarn，配置 2FA</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#session-%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%AE%BE%E7%BD%AE"><span class="toc-number">19.17.</span> <span class="toc-text">session 中间件设置</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#csurf-%E9%98%B2%E6%AD%A2-CSRF"><span class="toc-number">19.18.</span> <span class="toc-text">csurf 防止 CSRF</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8"><span class="toc-number">20.</span> <span class="toc-text">综合应用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#watch-%E6%9C%8D%E5%8A%A1"><span class="toc-number">20.1.</span> <span class="toc-text">watch 服务</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#RESTful-web-%E5%BA%94%E7%94%A8"><span class="toc-number">20.2.</span> <span class="toc-text">RESTful web 应用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%BA%94%E7%94%A8"><span class="toc-number">20.3.</span> <span class="toc-text">中间件应用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E8%BF%87%E4%BA%8B%E4%BB%B6%E7%BB%84%E7%BB%87%E5%BA%94%E7%94%A8"><span class="toc-number">20.4.</span> <span class="toc-text">通过事件组织应用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#WebSocket-%E4%B8%8E-session"><span class="toc-number">20.5.</span> <span class="toc-text">WebSocket 与 session</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Express4-%E4%B8%AD%E9%97%B4%E4%BB%B6"><span class="toc-number">20.6.</span> <span class="toc-text">Express4 中间件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#JWT"><span class="toc-number">20.7.</span> <span class="toc-text">JWT</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B7%A8%E5%9F%9F%E8%AE%A4%E8%AF%81"><span class="toc-number">20.7.1.</span> <span class="toc-text">跨域认证</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%80%E8%88%AC%E6%B5%81%E7%A8%8B"><span class="toc-number">20.7.1.1.</span> <span class="toc-text">一般流程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#session-%E5%85%B1%E4%BA%AB"><span class="toc-number">20.7.1.2.</span> <span class="toc-text">session 共享</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#JWT-1"><span class="toc-number">20.7.2.</span> <span class="toc-text">JWT</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8E%9F%E7%90%86"><span class="toc-number">20.7.2.1.</span> <span class="toc-text">原理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84"><span class="toc-number">20.7.2.2.</span> <span class="toc-text">数据结构</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#Header"><span class="toc-number">20.7.2.2.1.</span> <span class="toc-text">Header</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#Payload"><span class="toc-number">20.7.2.2.2.</span> <span class="toc-text">Payload</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#Signature"><span class="toc-number">20.7.2.2.3.</span> <span class="toc-text">Signature</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F"><span class="toc-number">20.7.2.3.</span> <span class="toc-text">使用方式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%89%B9%E7%82%B9"><span class="toc-number">20.7.2.4.</span> <span class="toc-text">特点</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#koa"><span class="toc-number">20.8.</span> <span class="toc-text">koa</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A0%B8%E5%BF%83%E5%AF%B9%E8%B1%A1"><span class="toc-number">20.8.1.</span> <span class="toc-text">核心对象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%BA%90%E7%A0%81%E7%BB%84%E6%88%90"><span class="toc-number">20.8.2.</span> <span class="toc-text">源码组成</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="toc-number">20.8.3.</span> <span class="toc-text">中间件的使用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E5%AA%92%E4%BD%93%E8%B5%84%E6%BA%90"><span class="toc-number">20.8.4.</span> <span class="toc-text">返回媒体资源</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Web-API-%E8%AE%BE%E8%AE%A1"><span class="toc-number">20.9.</span> <span class="toc-text">Web API 设计</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9C%80%E6%B1%82"><span class="toc-number">20.9.1.</span> <span class="toc-text">需求</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%87%8D%E8%A6%81%E5%87%86%E5%88%99"><span class="toc-number">20.9.2.</span> <span class="toc-text">重要准则</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%AC%E9%92%A5%E5%8A%A0%E5%AF%86%E7%A7%81%E9%92%A5%E8%A7%A3%E5%AF%86"><span class="toc-number">20.10.</span> <span class="toc-text">公钥加密私钥解密</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%94%9F%E6%88%90%E5%85%AC%E9%92%A5%E7%A7%81%E9%92%A5"><span class="toc-number">20.10.1.</span> <span class="toc-text">生成公钥私钥</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#crypto-%E4%BD%BF%E7%94%A8"><span class="toc-number">20.10.2.</span> <span class="toc-text">crypto 使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#redis-%E7%BC%93%E5%AD%98%E6%8E%A5%E5%8F%A3"><span class="toc-number">20.11.</span> <span class="toc-text">redis 缓存接口</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#redis-%E4%BD%BF%E7%94%A8"><span class="toc-number">20.11.1.</span> <span class="toc-text">redis 使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#node-schedule-%E4%BD%BF%E7%94%A8"><span class="toc-number">20.12.</span> <span class="toc-text">node-schedule 使用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B7%A8%E5%9F%9F%E5%85%B1%E4%BA%AB-Cookie"><span class="toc-number">20.13.</span> <span class="toc-text">跨域共享 Cookie</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BA%8C%E7%BA%A7%E5%9F%9F%E5%90%8D%E5%85%B1%E4%BA%AB-Cookie"><span class="toc-number">20.13.1.</span> <span class="toc-text">二级域名共享 Cookie</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B7%98%E5%AE%9D%E5%A4%A9%E7%8C%AB%E5%85%B1%E4%BA%AB-Cookie"><span class="toc-number">20.13.2.</span> <span class="toc-text">淘宝天猫共享 Cookie</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E5%9C%B0%E5%9D%80"><span class="toc-number">21.</span> <span class="toc-text">参考地址</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2023/07/22/window/win-11/" title="win 11 set">win 11 set</a><time datetime="2023-07-22T07:14:09.700Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2023/07/22/window/win-gitbash/" title="git bash">git bash</a><time datetime="2023-07-22T07:14:09.700Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2023/07/22/window/win-wsl/" title="wsl">wsl</a><time datetime="2023-07-22T07:14:09.700Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2023/07/22/window/win-main/" title="window">window</a><time datetime="2023-07-22T07:14:09.700Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2023/07/22/web/%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98/" title="兼容问题">兼容问题</a><time datetime="2023-07-22T07:14:09.699Z" title="发表于 2023-07-22 15:14:09">2023-07-22</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By lzoxun</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script>function panguFn () {
  if (typeof pangu === 'object') pangu.autoSpacingPage()
  else {
    getScript('https://cdn.jsdelivr.net/npm/pangu/dist/browser/pangu.min.js')
      .then(() => {
        pangu.autoSpacingPage()
      })
  }
}

function panguInit () {
  if (false){
    GLOBAL_CONFIG_SITE.isPost && panguFn()
  } else {
    panguFn()
  }
}

document.addEventListener('DOMContentLoaded', panguInit)</script><div class="js-pjax"></div><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-nest.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div class="no-result" id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js"></script></div></div><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"log":false,"model":{"jsonPath":"/live2dw/assets/haruto.model.json"},"display":{"position":"right","width":100,"height":200,"right":300},"mobile":{"show":true}});</script></body></html>